// lesson: barriers-rendezvous

Barriers and Rendezvous

sync.WaitGroup is a one-shot: N workers finish, one waiter proceeds. A cyclic barrier is the reusable, symmetric version โ€” N goroutines each call Await, everyone blocks until the Nth arrives, then all of them are released together and the barrier resets for the next round. Think lockstep simulation phases, parallel iterative solvers, or "everyone reloads config between batches".

The bug that kills naive implementations is generation mixing. A fast goroutine released from round k loops around and calls Await for round k+1 while a slow sibling from round k hasn't woken up yet. If your state is just a counter, the fast arrival corrupts the round the sleeper is still in. The standard fix is a generation number: waiters sleep while gen == myGen, and the last arrival increments gen, resets the count, and broadcasts.

sync.Cond fits perfectly here โ€” one mutex, Wait in a predicate loop, Broadcast from the last arrival. You can also do it with a channel per generation. Either way: nobody gets out early, and nobody's arrival leaks into the next round.

โ€บ Cyclic Barrier

30 pts

Implement:

func NewBarrier(n int) *Barrier
func (b *Barrier) Await()

Required semantics (n >= 1; every goroutine uses the barrier for its whole lifetime):

  • Await blocks until n goroutines (counting the caller) have called it in the current generation, then all n return and the barrier resets.
  • No early release: with only n-1 arrivals, nobody returns.
  • Reusable: the same Barrier must work for many consecutive generations, including when fast goroutines re-enter Await for the next generation while slow ones are still waking from the last. The tests run 4 goroutines through 25 generations and check an arrival counter per generation at every release.

Log in to submit a solution and earn points.