As a kind of test of some newer features of C++ I implemented a thread pool. I'm aware that there are probably libraries out there that already have thread pools implemented but this was just as a small test for myself. I'm looking for opinions on my implementation as well as suggestions on how to improve it if possible.
The current intention of it is just to have a simple way to add functions that should be executed to the pool without having to worry about the overhead of starting a new thread each time.
Here's the class:
typedef std::function<void(void)> work_function;
class ThreadPool {
private:
bool _running;
std::queue<work_function> _work_queue;
std::atomic<size_t> _num_work;
std::mutex _mutex;
std::vector<std::thread> _threads;
public:
ThreadPool(size_t num_threads = std::thread::hardware_concurrency());
virtual ~ThreadPool();
void addWork(work_function work);
void clearWork();
void wait();
};
An here's the implementation:
ThreadPool::ThreadPool(size_t num_threads) :
_running(true), _num_work(0) {
auto thread_loop = [&](size_t id) {
while (_running) {
_mutex.lock();
if (!_work_queue.empty()) {
auto work = _work_queue.front();
_work_queue.pop();
_mutex.unlock();
work();
_num_work--;
} else {
_mutex.unlock();
std::this_thread::yield();
}
}
};
_threads.reserve(num_threads);
for (size_t i = 0; i < num_threads; i++) {
_threads.push_back(std::thread(thread_loop, i));
}
}
ThreadPool::~ThreadPool() {
_running = false;
for (std::thread& t : _threads) {
t.join();
}
}
void ThreadPool::addWork(work_function work) {
_mutex.lock();
_work_queue.push(work);
_num_work++;
_mutex.unlock();
}
void ThreadPool::clearWork() {
std::queue<work_function> empty;
_mutex.lock();
_num_work -= _work_queue.size();
std::swap(_work_queue, empty);
_mutex.unlock();
}
void ThreadPool::wait() {
while (_num_work.load() > 0) {
std::this_thread::yield();
}
}
std::async()
. Threads and thread pools are way too low level. You should not be using them unless you have tested and defined arguments that you can validate for using them. Modern code should be using the modern interface to concurrency.std::async()
/std::promise
/std::future
\$\endgroup\$std::async
has to be implemented and that in almost no implementations it works with a thread pool but just launches threads instead. \$\endgroup\$the standard doesn't tell how std::async has to be implemented
: Of course. The standard never specifies how something should be implemented. It specifies the affects thus allowing implementations to evolve and become better. None of this changes my original comment. though. \$\endgroup\$