Search⌘ K
AI Features

Additional Synchronization Primitives in C++20

Understand how to use new synchronization primitives introduced in C++20 such as std::latch, std::barrier, and counting semaphores. Learn practical examples including thread initialization with latches, fork-join parallelism with barriers, and thread-local random number generation, all aimed at mastering concurrent programming.

C++20 comes with a few additional synchronization primitives, namely std::latch, std::barrier, and std::counting_semaphore (and the template specialization std::binary_semaphore). This lesson will be an overview of these new types and some typical scenarios where they can be useful. We’ll begin with std::latch.

Using latches

A latch is a synchronization primitive that can be used for synchronizing multiple threads. It creates a synchronization point where all threads must arrive at. We can think of a latch as a decrementing counter. Typically, all threads decrement the counter once and then wait for the latch to reach zero before moving on.

A latch is constructed by passing an initial value of the internal counter:

C++
auto lat = std::latch{8}; // Construct a latch initialized with 8

Threads can then decrement the counter using count_down():

C++
lat.count_down(); // Decrement but don't wait

A thread can wait on the latch to reach zero:

C++
lat.wait(); // Block until zero

It’s also possible to check (without blocking) to see whether the counter has reached zero:

C++
if (lat.try_wait()) {
// All threads have arrived ...
}

It’s common to wait for the latch to reach zero right after decrementing the counter, as follows:

C++
lat.count_down();
lat.wait();

In fact, this use case is common enough to deserve a tailor-made member function; arrive_and_wait() decrements the latch and then waits for the latch to reach zero:

C++
lat.arrive_and_wait(); // Decrement and block while not zero

Joining a set of forked tasks is a common scenario when working with ...