How it works...

Similarly to the application that we created in the Exploring data synchronization recipe, we create two worker threads, worker1 and worker2, that use the same worker function thread and differ only by the index parameter.

Besides printing messages to the console, the worker thread update a global vector result. Each worker just adds its index into the result variable in its loop, as shown in the following command:

std::vector<int> result;

We want each worker to add its index to the result only on its turn— worker 1, then worker 2, then worker 1 again, and so on. It is not possible to do this without synchronization; however, simple synchronization using mutexes is not sufficient. It can guarantee that two concurrent threads will not access the same critical section of the code at the same time, but cannot guarantee the order. It is possible that worker 1 will lock the mutex again before worker 2 locks it.

To solve the ordering problem, we define a cv condition variable and a next integer variable:

std::condition_variable cv;
int next = 0;

The next variable contains an index of the worker. It is initialized with 0 and set to a specific worker index in the main function. Since this variable is accessed from multiple threads, we do it under the protection of the lock guard:

  {
std::lock_guard<std::mutex> l(m);
next = 1;
}

Though the worker threads start executing after their creation, both of them are immediately blocked on the condition variables, waiting until the value of the next variable matches their index. Condition variables need std::unique_lock for waiting. We create it right before calling the wait method:

std::unique_lock<std::mutex> l(m);
cv.wait(l, [=]{return next == index; });

Though the condition variable cv was set to  1 in the main function, it is not enough. We need to explicitly notify threads waiting on the condition variable. We do this using the notify_all method:

cv.notify_all();

This wakes up all waiting threads, and they compare their index against the next variable. The matching thread unblocks, and all other threads go to sleep again.

The active thread writes a message to the console and updates the result variable. Then, it updates the next variable to choose a thread that will be activated next. We increment the index until it reaches the maximum value, then reset it to 1:

next = next + 1;
if (next > 2) { next = 1; };

Similar to the case with the code in the main function, after the index of the next thread is decided, we need to invoke notify_all to wake all threads up and let them decide whose turn it is to work:

cv.notify_all();

While the worker threads work, the main function waits for their completion:

 worker1.join();
worker2.join();

When all worker threads complete, the value of the result variable  is printed:

  for (int e : result) {
std::cout << e << ' ';
}

After we build and run our program, we get the following output:

As we can see, all threads were activated in the expected order.