C++20 Concurrency: std::barrier

A reusable alternative to std::latch

CMP
2 min readJun 20

--

I previously wrote about using std::latch to synchronize multiple threads. While that is a great mechanism for simple one-time synchronization scenarios where multiple threads need to meet at a synchronization point before continuing execution, it is less flexible than the other new C++20 concurrency primitive: std::barrier.

What is std::barrier?

Like std::latch, std::barrier is a synchronization mechanism that allows a fixed number of threads to synchronize at a certain point in their execution. However, there are a few key differences:

  • std::barrier is reusable: While std::latch can only be used one time (once the down-counter reaches 0, it becomes unusable), std::barrier can be reused multiple times. Once all threads have reached the sync point, the barrier resets for the next iteration.
  • std::barrier has a callback function: After the sync point is reached by all threads, the std::barrier executes a potentially empty callback function.
  • Slightly different “wait” behavior: std::latch uses count_down() to decrement its counter and wait() to block until the counter reaches 0, while std::barrier typically uses arrive_and_wait() to both decrement the counter and block. It is possible to do these steps independently (arrive() then wait() ) if desired.

Below is the example that I used previously for std::latch:

std::latch latch(3);

void worker() {
std::cout << "Starting worker thread" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Worker thread reached synchronization point" << std::endl;
latch.count_down();
// ...
}

int main() {
std::thread t1(worker);
std::thread t2(worker);
std::thread t3(worker);

latch.wait(); // Wait until the latch's internal counter reaches 0

t1.join();
t2.join();
t3.join();

std::cout << "All worker threads reached the synchronization point. Exiting..." << std::endl;

return 0;
}

This can be rewritten to use std::barrier instead:

std::barrier barrier(3); 

void worker() {
std::cout << "Starting worker thread" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Worker thread reached synchronization point" << std::endl;
barrier.arrive_and_wait(); // wait until all threads have reached the barrier
// ...
}

int main() {
std::thread t1(worker);
std::thread t2(worker);
std::thread t3(worker);

t1.join();
t2.join();
t3.join();

std::cout << "All worker threads reached the synchronization point. Exiting..." << std::endl;

return 0;
}

Because the barrier resets after all three threads arrive, if I wanted to call worker() on all three threads again, I could.

--

--