0

C++20 introduces barriers, where a specified number of threads must arrive at the barrier, by calling arrive_and_wait(). Once all have arrived a completion function is called from one thread to reset the barrier. Then all the threads are released, perhaps to iterate and hit the barrier again. An example of barrier usage is found at https://en.cppreference.com/w/cpp/thread/barrier . How would you cleanly implement this same example in C++14? C++17 would also be of interest, but my constraint is really C++14.

Note that I am able to use C++20 in my VS 2019 by setting /std:c++latest but I have to also support VS 2015, which I assume doesn't offer std::barrier.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
All The Rage
  • 743
  • 5
  • 24
  • 2
    This sounds like a simple combination of a mutex, a condition variable, and a counter. Eminently reimplementable in the straightforward fashion. Forget about C++17 or C++14, these basic blocks existed since C++11. What, specifically, about the direct reimplementation of this algorithm is unclear to you? – Sam Varshavchik Jun 29 '21 at 15:01
  • @SamVarshavchik: Well, `barrier` is lock-less in some scenarios (if all of the threads have arrived, any `wait` calls are required to immediately proceed), so mutex-based implementation is at least partially out. It's not as trivial to implement as it sounds. – Nicol Bolas Jun 29 '21 at 15:04
  • 3
    Where do you see, in the sited documentation, anything about "lock-less"? Just because that in some situations there is no waiting doesn't mean that there isn't a lock that gets briefly acquired and released. When it comes to discussions related to interprocess synchronization, a lack of "waiting" doesn't mean that the execution thread doesn't briefly wait to acquire a lock, if this lock is never held by any process for any prolonged period of time. – Sam Varshavchik Jun 29 '21 at 15:06
  • Since you are using MSVC maybe checkout: https://learn.microsoft.com/en-us/windows/win32/Sync/synchronization-functions – Matt Eding Jun 29 '21 at 15:14
  • Boost has [barrier](https://www.boost.org/doc/libs/1_76_0/doc/html/thread/synchronization.html#thread.synchronization.barriers). Probably not optimal, but good enough as a fallback. Or something like [this](https://stackoverflow.com/questions/24205226/how-to-implement-a-re-usable-thread-barrier-with-stdatomic) perhaps. – rustyx Jun 29 '21 at 15:15
  • Do you need the entire functionality of `barrier`, or some subset? If so, can you provide the subset you need? Are you using `CompletionFunction`? Are you using all of its methods? – Yakk - Adam Nevraumont Jun 29 '21 at 15:23
  • @SamVarshavchik: Wait-free is a stronger condition than lock-free. It does indeed guarantee that "the execution thread doesn't briefly wait to acquire a lock". – Ben Voigt Jun 29 '21 at 15:51
  • Thanks all. I'm looking through the replies and trying them out. @Adam Nevraumont, the subset I need is precisely the subset used in the example at the link. So arrive_and_wait() and calling the completion function. – All The Rage Jun 29 '21 at 17:03

1 Answers1

1

The sample code uses only arrive_and_wait and the constructor, plus the completion function.

struct Barrier {
  mutable std::mutex m;
  std::condition_variable cv;
  std::size_t size;
  std::ptrdiff_t remaining;
  std::ptrdiff_t phase = 0;
  std::function<void()> completion;
  Barrier( std::size_t s, std::function<void()> f ):
    size(s), remaining(s), completion(std::move(f))
  {}
  void arrive_and_wait()
  {
    auto l = std::unique_lock(m);
    --remaining;
    if (remaining != 0)
    {
      auto myphase = phase+1;
      cv.wait(l, [&]{
        return myphase - phase <= 0;
      });
    }
    else
    {
      completion();
      remaining = size;
      ++phase;
      cv.notify_all();
    }
  }
  void arrive_and_drop()
  {
      auto l = std::unique_lock(m);
      --size;
      --remaining;
      if (remaining == 0) {
          completion();
          remaining = size;
          ++phase;
          cv.notify_all();
      }
  }



};

or something like that.

All The Rage
  • 743
  • 5
  • 24
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Thanks. This implementation was almost perfect. All I had to add was initialization of remaining. I also implemented arrive_and_drop(). Thanks. – All The Rage Jul 06 '21 at 20:18