C++20 Concurrency: std::latch

A convenient way to coordinate concurrent tasks

CMP
2 min readJun 14

--

When running multiple tasks in concurrent threads, you may wish to ensure that each thread has reached a certain point in its execution before proceeding further. Prior to C++20, you would need some sort of shared atomic counter to achieve this:

std::atomic<int> counter(0); // Shared atomic counter

void worker() {
std::cout << "Starting worker thread" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
count.fetch_add(1); // Increase counter by 1
std::cout << "Worker thread reached synchronization point" << std::endl;
// ...
}

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

while (count < 3) {
// Wait until the counter reaches the number of threads
}

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

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

return 0;
}

Although there is nothing wrong with this code, there is a more intuitive and extensible approach with C++ using std::latch.

What is std::latch?

std::latch is a downward counter used to synchronize threads. Like the shared atomic counter in the example above, threads can block on a std::latch until they reach a synchronization point. Here is an updated version of the above example:

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;
}

As you can see, using std::latch provides a clear, intuitive abstraction for coordinating concurrent tasks compared to using an atomic counter variable. The std::latch class also provides other functions, such as try_wait() to test if the internal counter reaches 0. Furthermore, multiple threads can safely call wait() and count_down() without any additional concurrency primitives like std::mutex.

One point to keep in mind is that in order to eliminate the possibility of data races, std::latch is single-use. There is no way to increase or reset the counter, so the latch can only be used until its internal counter reaches 0.

--

--