I have written a small and simple ThreadPool
class for rather simple multithreading applications.
- The
Threadpool
class manages a vector with the actual threads. - The
Thread
struct is used to store the thread's function. ThreadPoolElement
keeps track of the thread and its state.
#pragma once
#include <vector>
#include <thread>
#include <mutex>
namespace frm { namespace util {
template <typename F, typename ... Args>
struct Thread {
Thread(F&& f)
: func(f) {
}
std::function<void(Args...)> func;
};
class Threadpool {
public:
Threadpool(const size_t& capasity = std::thread::hardware_concurrency())
: m_capacity(capasity) {
m_threads.reserve(m_capacity);
}
virtual ~Threadpool() {
for (auto& t : m_threads) {
t.thread.join();
}
}
template <typename Thread, typename ... Args>
void getThread(Thread& t, Args... args) {
while (m_threads.size() >= m_capacity) {
cleanup();
}
std::lock_guard<std::mutex> lock(m_mutex);
m_threads.emplace_back(ThreadPoolElement());
ThreadPoolElement& elem = m_threads[m_threads.size() - 1];
elem.thread = std::thread([&] (ThreadPoolElement* e, Thread& t, Args... args) {
t.func(args...);
e->is_done = true;
}, &elem, t, args...);
}
size_t activeThreads() const {
return m_threads.size();
}
size_t reservedThreads() const {
return m_capacity;
}
void cleanup() {
for (int i = 0; i < m_threads.size(); i++) {
if (m_threads[i].is_done) {
std::lock_guard<std::mutex> lock(m_mutex);
m_threads[i].thread.join();
m_threads.erase(m_threads.begin() + i--);
}
}
}
private:
struct ThreadPoolElement {
std::thread thread;
volatile bool is_done = false;
};
size_t m_capacity;
std::mutex m_mutex;
std::vector<ThreadPoolElement> m_threads;
};
} }
My questions are pretty simple:
Has this implementation any significant downsides that would make it a 'Please do not use this' implementation?
What are the most important parts I'm missing?