This is my first time writing a custom thread pool in C++. I would love some feedback on how my code looks and if I am missing best practices or have glaring/subtle issues. Thanks!
ThreadPool.hpp:
#pragma once
#include <atomic>
#include <vector>
#include <iostream>
#include <thread>
#include <future>
#include <mutex>
#include <queue>
#include <functional>
#include <ThreadSafeQueue.hpp>
class ThreadPool{
public:
ThreadPool(std::atomic_bool& result);
void waitForCompletion();
void addJob(std::function<bool()> newJob);
void setComplete();
private:
void workLoop(std::atomic_bool& result);
int m_numThreads;
std::vector<std::thread> m_threads;
std::atomic_bool m_workComplete;
std::mutex m_mutex;
std::condition_variable m_jobWaitCondition;
ThreadSafeQueue<std::function<bool()>> m_JobQueue;
};
ThreadPool.cpp:
#include <ThreadPool.hpp>
ThreadPool::ThreadPool(std::atomic_bool& result){
m_numThreads = std::thread::hardware_concurrency();
m_workComplete = false;
for (int i = 0; i < m_numThreads; i++)
{
m_threads.push_back(std::thread(&ThreadPool::workLoop, this, std::ref(result)));
}
}
// each thread executes this loop
void ThreadPool::workLoop(std::atomic_bool& result){
while(!m_workComplete){
std::function<bool()> currentJob;
bool popped;
{
std::unique_lock<std::mutex> lock(m_mutex);
m_jobWaitCondition.wait(lock, [this](){
return !m_JobQueue.empty() || m_workComplete.load();
});
popped = m_JobQueue.pop(currentJob);
}
if(popped){
result = currentJob() && result;
}
}
}
void ThreadPool::addJob(std::function<bool()> newJob){
m_JobQueue.push(newJob);
m_jobWaitCondition.notify_one();
}
void ThreadPool::setComplete(){
m_workComplete = true;
}
void ThreadPool::waitForCompletion(){
{
std::unique_lock<std::mutex> lock(m_mutex);
m_workComplete.store(true);
}
m_jobWaitCondition.notify_all();
for(auto& thread : m_threads){
thread.join();
}
m_threads.clear();
}
ThreadSafeQueue.hpp:
#pragma once
#include <mutex>
#include <queue>
template <class T>
class ThreadSafeQueue {
public:
ThreadSafeQueue(){};
void push(T element) {
std::unique_lock<std::mutex> lock(m_mutex);
m_queue.push(element);
}
bool pop(T& retElement) {
std::unique_lock<std::mutex> lock(m_mutex);
if (m_queue.empty()) {
return false;
}
retElement = m_queue.front();
m_queue.pop();
return true;
}
bool empty(){
std::unique_lock<std::mutex> lock(m_mutex);
return m_queue.empty();
}
private:
std::queue<T> m_queue;
std::mutex m_mutex;
};
```