C++ Thread synchronization pitfall: using a barrier to synchronize a thread start


The context:

You want to create a worker thread. Before the main thread goes on, you want to ensure that the worker thread starts.

Using a boost::barrier seems like a good idea.

It would look like:

class Worker
{
public:
    Worker() : thread_() { }
 
    void run(boost::barrier &barrier)
    {
        // init
        barrier.wait();
        // work
    }
 
    void start()
    {
        boost::barrier ready(2);
        thread_ = std::thread(&Worker::run, this, std::ref(ready));
        ready.wait();
    }
 
private:
    std::thread thread_;
};

A problem occurs with this code when the following sequence appears:

  • Main: calls start()
  • Main start() creates thread
  • start() waits on ready barrier
  • Worker starts in run()
  • Worker run() waits on ready barrier

Since it is the second call to barrier wait(), both threads are now free to execute. Note that there might still be something to do in the barrier wait() call on each side.

If Main gets the CPU,

  • barrier wait() call ends
  • start() method ends
  • ready barrier is destroyed

Later, Worker wants to continue but the barrier wait() call is now referring to a dead barrier.

The solution: put the barrier in a shared_ptr. This ensures that it will only be destroyed when it is not needed anymore.

class Worker
{
public:
    Worker() : thread_() { }
 
    void run(std::shared_ptr<boost::barrier> barrier)
    {
        // init
        barrier->wait();
        barrier.reset();
        // work
    }
 
    void start()
    {
        std::shared_ptr<boost::barrier> ready = std::make_shared<boost::barrier>(2);
        thread_ = std::thread(&Worker::run, this, ready);
        ready->wait();
    }
 
private:
    std::thread thread_;
};

Thanks to my colleague Christophe Bourez for enlighting us on this issue.

,

Les commentaires sont fermés.