In C++ do you need to lock a mutex before assigning to an atomic? I tried implementing the thread pool as shown here https://stackoverflow.com/a/32593825/2793618. In doing so, I created a thread safe queue and used atomics. In particular, in the shutdown method (or in my code the waitForCompletion) requires the thread pool loop function while loop variable to be set to true so that the thread can finish its work and join. But since atomics are thread safe, I didn't lock the mutex before assigning true to it in the shutdown method as shown below. This ended up causing a deadlock. Why is that the case?
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;
};