I'm new to std::atomic
, std::mutex
, std::unique_lock
, std::condition_variable
and more or less c++11 std::thread
, so I wrote this little Job_Queue class, where you can submit a void()
function and it will be distributed among some threads. I wonder if there's room for improvement or if I made to beginner's mistakes. The idea is, that you can have multiple job_queues waiting, but not running at the same time.
job_queue.hpp
#ifndef SNIPPETS_JOB_QUEUE_HPP
#define SNIPPETS_JOB_QUEUE_HPP
#include <atomic>
#include <condition_variable>
#include <functional>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>
class Job_Queue
{
public:
static std::mutex job_mutex;
void start();
void stop();
void terminate();
Job_Queue();
void add(const std::function<void()>& f);
void add(std::function<void()>&& f);
private:
static bool is_running;
static unsigned int n_cores;
std::atomic<bool> terminate_after_current_job;
std::atomic<bool> stop_when_queue_is_empty;
std::vector<std::thread> threads;
std::condition_variable thread_cv;
std::mutex queue_mutex;
std::queue<std::function<void()>> job_queue;
void thread_loop();
};
#endif //SNIPPETS_JOB_QUEUE_HPP
job_queue.cpp
#include "job_queue.hpp"
void Job_Queue::start()
{
if( is_running )
{
std::cerr << "Error: Another JobQueue is currently running. Please end it first.\n";
return;
}
is_running = true;
threads.clear();
terminate_after_current_job = false;
stop_when_queue_is_empty = false;
threads.reserve(n_cores);
for( unsigned int k = 0; k < n_cores; ++k )
{
threads.emplace_back(&Job_Queue::thread_loop, this);
}
}
void Job_Queue::terminate()
{
terminate_after_current_job = true;
stop();
}
void Job_Queue::stop()
{
stop_when_queue_is_empty = true;
thread_cv.notify_all();
for( unsigned int k = 0; k < n_cores; ++k )
{
thread_cv.notify_all();
threads[k].join();
}
threads.clear();
is_running = false;
}
void Job_Queue::add(const std::function<void()>& f)
{
job_queue.push(f);
thread_cv.notify_all();
}
void Job_Queue::add(std::function<void()>&& f)
{
job_queue.emplace(f);
thread_cv.notify_all();
}
Job_Queue::Job_Queue()
: queue_mutex(std::mutex())
{
if ( Job_Queue::n_cores == 0)
{
n_cores = std::thread::hardware_concurrency();
if (n_cores == 0)
{
std::cerr << "Error: Could not find number of available cores.\n";
}
}
}
void Job_Queue::thread_loop()
{
{
std::unique_lock<std::mutex> mu(Job_Queue::job_mutex);
}
bool job_q_empty = false;
while( true )
{
{
std::unique_lock<std::mutex> lock(queue_mutex);
thread_cv.wait(lock, [this, &job_q_empty] { job_q_empty = job_queue.empty(); return !job_queue.empty() || terminate_after_current_job || stop_when_queue_is_empty; });
}
if( !job_q_empty )
{
std::function<void()> f;
{
std::unique_lock<std::mutex> lock(queue_mutex);
f = job_queue.front();
job_queue.pop();
std::cout << "jobs left in Q: " << job_queue.size() << "\n";
}
f();
}
else
{
if( stop_when_queue_is_empty )
{
break;
}
}
if( terminate_after_current_job )
{
break;
}
}
}
std::mutex Job_Queue::job_mutex = std::mutex();
bool Job_Queue::is_running = false;
unsigned int Job_Queue::n_cores = 0;
main.cpp
#include "job_queue.hpp"
void short_job(std::size_t i)
{
std::this_thread::sleep_for(std::chrono::seconds(5));
std::unique_lock<std::mutex> lock(Job_Queue::job_mutex);
std::cout << "Finished S job: " << i << " on thread: " << std::this_thread::get_id() << "\n" << std::flush;
}
void middle_job(std::size_t i)
{
std::this_thread::sleep_for(std::chrono::seconds(10));
std::unique_lock<std::mutex> lock(Job_Queue::job_mutex);
std::cout << "Finished M job: " << i << " on thread: " << std::this_thread::get_id() << "\n" << std::flush;
}
void long_job(std::size_t i)
{
std::this_thread::sleep_for(std::chrono::seconds(15));
std::unique_lock<std::mutex> lock(Job_Queue::job_mutex);
std::cout << "Finished L job: " << i << " on thread: " << std::this_thread::get_id() << "\n" << std::flush;
}
int main()
{
Job_Queue job_queue;
job_queue.start();
for( std::size_t k = 0; k < 100; ++k )
{
if( k%3 == 0 )
{
job_queue.add(std::move(std::bind(short_job, k)));
}
else if( k%3 == 1 )
{
job_queue.add(std::move(std::bind(middle_job, k)));
}
else
{
job_queue.add(std::move(std::bind(long_job, k)));
}
}
job_queue.stop();
return 0;
}