Reusing thread in loop c++
The easiest way is to use a waitable queue of std::function
objects. Like this:
#include <iostream>#include <thread>#include <mutex>#include <condition_variable>#include <queue>#include <functional>#include <chrono>class ThreadPool{ public: ThreadPool (int threads) : shutdown_ (false) { // Create the specified number of threads threads_.reserve (threads); for (int i = 0; i < threads; ++i) threads_.emplace_back (std::bind (&ThreadPool::threadEntry, this, i)); } ~ThreadPool () { { // Unblock any threads and tell them to stop std::unique_lock <std::mutex> l (lock_); shutdown_ = true; condVar_.notify_all(); } // Wait for all threads to stop std::cerr << "Joining threads" << std::endl; for (auto& thread : threads_) thread.join(); } void doJob (std::function <void (void)> func) { // Place a job on the queu and unblock a thread std::unique_lock <std::mutex> l (lock_); jobs_.emplace (std::move (func)); condVar_.notify_one(); } protected: void threadEntry (int i) { std::function <void (void)> job; while (1) { { std::unique_lock <std::mutex> l (lock_); while (! shutdown_ && jobs_.empty()) condVar_.wait (l); if (jobs_.empty ()) { // No jobs to do and we are shutting down std::cerr << "Thread " << i << " terminates" << std::endl; return; } std::cerr << "Thread " << i << " does a job" << std::endl; job = std::move (jobs_.front ()); jobs_.pop(); } // Do the job without holding any locks job (); } } std::mutex lock_; std::condition_variable condVar_; bool shutdown_; std::queue <std::function <void (void)>> jobs_; std::vector <std::thread> threads_;};void silly (int n){ // A silly job for demonstration purposes std::cerr << "Sleeping for " << n << " seconds" << std::endl; std::this_thread::sleep_for (std::chrono::seconds (n));}int main(){ // Create two threads ThreadPool p (2); // Assign them 4 jobs p.doJob (std::bind (silly, 1)); p.doJob (std::bind (silly, 2)); p.doJob (std::bind (silly, 3)); p.doJob (std::bind (silly, 4));}
The std::thread
class is designed to execute exactly one task (the one you give it in the constructor) and then end. If you want to do more work, you'll need a new thread. As of C++11, that's all we have. Thread pools didn't make it into the standard. (I'm uncertain what C++14 has to say about them.)
Fortunately, you can easily implement the required logic yourself. Here is the large-scale picture:
- Start n worker threads that all do the following:
- Repeat while there is more work to do:
- Grab the next task t (possibly waiting until one becomes ready).
- Process t.
- Repeat while there is more work to do:
- Keep inserting new tasks in the processing queue.
- Tell the worker threads that there is nothing more to do.
- Wait for the worker threads to finish.
The most difficult part here (which is still fairly easy) is properly designing the work queue. Usually, a synchronized linked list (from the STL) will do for this. Synchronized means that any thread that wishes to manipulate the queue must only do so after it has acquired a std::mutex
so to avoid race conditions. If a worker thread finds the list empty, it has to wait until there is some work again. You can use a std::condition_variable
for this. Each time a new task is inserted into the queue, the inserting thread notifies a thread that waits on the condition variable and will therefore stop blocking and eventually start processing the new task.
The second not-so-trivial part is how to signal to the worker threads that there is no more work to do. Clearly, you can set some global flag but if a worker is blocked waiting at the queue, it won't realize any time soon. One solution could be to notify_all()
threads and have them check the flag each time they are notified. Another option is to insert some distinct “toxic” item into the queue. If a worker encounters this item, it quits itself.
Representing a queue of tasks is straight-forward using your self-defined task
objects or simply lambdas.
All of the above are C++11 features. If you are stuck with an earlier version, you'll need to resort to third-party libraries that provide multi-threading for your particular platform.
While none of this is rocket science, it is still easy to get wrong the first time. And unfortunately, concurrency-related bugs are among the most difficult to debug. Starting by spending a few hours reading through the relevant sections of a good book or working through a tutorial can quickly pay off.
This
std::thread acq1(...)
is the call of an constructor. constructing a new object called acq1
This
acq1(...)
is the application of the () operator on the existing object aqc1. If there isn't such a operator defined for std::thread the compiler complains.
As far as I know you may not reused std::threads. You construct and start them. Join with them and throw them away,