Barrier Synchronization is the parallel coordination primitive where all threads (or processes) in a group must reach the barrier point before any thread is allowed to proceed past it — enforcing a global synchronization point that separates phases of computation, ensuring that all results from phase K are complete before phase K+1 begins, at the cost of idle time equal to the delay of the slowest thread.
Why Barriers Are Necessary
Many parallel algorithms have phases: scatter data, compute locally, exchange results, compute again. Without a barrier between phases, a fast thread might start phase K+1 before a slow thread has finished phase K, reading incomplete or inconsistent data. The barrier guarantees phase ordering.
Barrier Implementations
- Centralized Counter Barrier: A shared counter initialized to N (number of threads). Each arriving thread atomically decrements the counter. When the counter reaches 0, all threads proceed. Simple but does not scale — the shared counter creates a serialization bottleneck and cache line bouncing among cores.
- Tree Barrier: Threads are organized in a binary tree. At each level, pairs of threads synchronize locally, then one continues up the tree. After the root receives all arrivals, a wake-up propagates down the tree. O(log N) steps, excellent scalability. MCS barrier (Mellor-Crummey & Scott) is the standard tree barrier implementation.
- Butterfly (Tournament) Barrier: In round k, thread i synchronizes with thread i XOR 2^k. After log(N) rounds, all threads are globally synchronized. Each round involves only pairwise communication — ideal for distributed-memory systems where communication is point-to-point.
- GPU Thread Block Barrier (__syncthreads): Hardware-supported barrier within a CUDA thread block. All threads in the block reach __syncthreads() before any proceeds. Near-zero overhead (1-2 cycles when all threads arrive simultaneously). Does NOT synchronize across different thread blocks.
- GPU Grid-Level Barrier: Synchronizing all thread blocks requires kernel launch boundaries (implicit barrier) or cooperative groups with grid.sync() (requires occupancy guarantees). The kernel launch overhead (~5-20 us) makes grid-level barriers expensive.
Barrier Overhead and Mitigation
Barrier time = max(thread completion times) — min(thread completion times) + synchronization overhead. The cost of a barrier is the load imbalance it exposes — the fastest thread wastes time waiting for the slowest.
Reduction Strategies
- Reduce Barrier Frequency: Combine multiple phases between barriers when dependencies allow.
- Point-to-Point Synchronization: Replace global barriers with fine-grained dependencies. Thread A only waits for Thread B (its data source), not all threads.
- Fuzzy Barriers: Separate the "arrival" (I'm done producing) from the "departure" (I need to consume). A thread can do useful work between announcing arrival and needing departure permission.
Barrier Synchronization is the metronome of parallel computation — the synchronization heartbeat that keeps parallel threads marching in phase, at the cost of forcing the fastest threads to wait for the slowest, making barrier overhead the direct measure of load imbalance in a parallel program.