std::barrier(3)

NAME
std::barrier - std::barrier

Synopsis
   Defined in header <barrier>
   template<class CompletionFunction = /* see below */> (since C++20)
   class barrier;
   The class template std::barrier provides a thread-coordination mechanism that allows
   at most an expected number of threads to block until the expected number of threads
   arrive at the barrier. Unlike std::latch, barriers are reusable: once the arriving
   threads are unblocked from a barrier phase's synchronization point, the same barrier
   can be reused.
   A barrier object's lifetime consists of a sequence of barrier phases. Each phase
   defines a phase synchronization point. Threads that arrive at the barrier during the
   phase can block on the phase synchronization point by calling wait, and will be
   unblocked when the phase completion step is run.
   A barrier phase consists following steps:
    1. The expected count is decremented by each call to arrive or arrive_and_drop.
    2. When the expected count reaches zero, the phase completion step is run. The
       completion step invokes the completion function object, and unblocks all threads
       blocked on the phase synchronization point. The end of the completion step
       strongly happens-before the returns from all calls that were unblocked by the
       completion step.
          * For the specialization std::barrier<> (using the default template
            argument), the completion step is run as part of the call to arrive or
            arrive_and_drop that caused the expected count to reach zero.
          * For other specializations, the completion step is run on one of the threads
            that arrived at the barrier during the phase. And the behavior is undefined
            if any of the barrier object's member functions other than wait are called
            during the completion step.
    3. When the completion step finishes, the expected count is reset to the value
       specified at construction less the number of calls to arrive_and_drop since, and
       the next barrier phase begins.
   Concurrent invocations of the member functions of barrier, except for the
   destructor, do not introduce data races.

Template parameters
   CompletionFunction - a function object type
   -
   CompletionFunction must meet the requirements of MoveConstructible and Destructible.
   std::is_nothrow_invocable_v<CompletionFunction&> must be true.
   The default template argument of CompletionFunction is an unspecified function
   object type that addtionally meets the requirements of DefaultConstructible. Calling
   an lvalue of it with no arguments has no effects.
   Every barrier object behaves as if it holds an exposition-only non-static data
   member completion_ of type CompletionFunction and calls it by completion_() on every
   phase completion step.

Member types
   Name Definition
   arrival_token an unspecified object type meeting requirements of MoveConstructible,
                 MoveAssignable and Destructible

Member functions
   constructor constructs a barrier
                   (public member function)
   destructor destroys the barrier
                   (public member function)
   operator= barrier is not assignable
   [deleted] (public member function)
   arrive arrives at barrier and decrements the expected count
                   (public member function)
                   blocks at the phase synchronization point until its phase completion
   wait step is run
                   (public member function)
                   arrives at barrier and decrements the expected count by one, then
   arrive_and_wait blocks until current phase completes
                   (public member function)
                   decrements both the initial expected count for subsequent phases and
   arrive_and_drop the expected count for current phase by one
                   (public member function)

Constants
   max the maximum value of expected count supported by the implementation
   [static] (public static member function)

Notes
   Feature-test macro: __cpp_lib_barrier

Example
// Run this code
 #include <barrier>
 #include <iostream>
 #include <string>
 #include <thread>
 #include <vector>
 int main() {
   const auto workers = { "anil", "busara", "carl" };
   auto on_completion = []() noexcept {
     // locking not needed here
     static auto phase = "... done\n" "Cleaning up...\n";
     std::cout << phase;
     phase = "... done\n";
   };
   std::barrier sync_point(std::ssize(workers), on_completion);
   auto work = [&](std::string name) {
     std::string product = " " + name + " worked\n";
     std::cout << product; // ok, op<< call is atomic
     sync_point.arrive_and_wait();
     product = " " + name + " cleaned\n";
     std::cout << product;
     sync_point.arrive_and_wait();
   };
   std::cout << "Starting...\n";
   std::vector<std::thread> threads;
   for (auto const& worker : workers) {
     threads.emplace_back(work, worker);
   }
   for (auto& thread : threads) {
     thread.join();
   }
 }

Possible output:
 Starting...
   anil worked
   carl worked
   busara worked
 ... done
 Cleaning up...
   busara cleaned
   carl cleaned
   anil cleaned
 ... done

See also
   latch single-use thread barrier
   (C++20) (class)