Reusing thread in loop c++ Reusing thread in loop c++ multithreading multithreading

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.
  • 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,