Python threading.Barrier for Cyclic Coordination
Python's threading.Barrier is a synchronization
primitive designed to coordinate a predefined number of threads by
making them wait at a designated point until every thread has arrived.
This article explores how threading.Barrier functions, how
its built-in cyclic nature allows threads to proceed through repeating
computational phases without needing to reconstruct the barrier, and how
features like action callbacks and timeout handling ensure robust state
synchronization in multi-threaded workflows.
Understanding the Barrier Mechanism
A barrier is instantiated with an integer specifying the number of "parties" (threads) required to trip it:
import threading
barrier = threading.Barrier(parties=3)When a worker thread completes its share of work for a given stage,
it invokes barrier.wait(). This call blocks the calling
thread. Internally, the barrier increments an arrival counter. As long
as this counter is less than the specified number of
parties, each arriving thread remains blocked in a wait
state.
Once the final thread calls barrier.wait(), the arrival
threshold is satisfied. The barrier unblocks all waiting threads
simultaneously, allowing them to resume execution in parallel.
The Cyclic Feature: Seamless Phased Execution
Unlike one-time synchronization events (such as
threading.Event), threading.Barrier is
inherently cyclic. Once the required number of threads passes the
barrier, the internal counter automatically resets to zero.
This automatic reset enables multi-phased algorithms, where threads repeatedly alternate between independent local computations and collective synchronization points. A typical workflow involves:
- Phase 1 (Execution): All threads compute intermediate values independently.
- Phase 1 (Sync): Each thread calls
barrier.wait(). - Transition: Once all threads arrive, the barrier releases them and resets automatically.
- Phase 2 (Execution): Threads read the synchronized results from Phase 1 and begin the next phase.
Because the barrier cycles automatically, you can place
barrier.wait() inside a loop to orchestrate lockstep
iterations without race conditions during the reset phase.
Action Callbacks at Phase Transitions
The threading.Barrier constructor accepts an optional
action parameter. This callable runs exactly once per cycle
when the barrier is tripped, executed by whichever thread triggers the
barrier release before any of the threads are unblocked.
def summarize_phase():
print("Phase complete. Aggregating results before next step.")
barrier = threading.Barrier(parties=3, action=summarize_phase)This ensures that state aggregation, data normalization, or phase transitions occur safely without competing writes or requiring extra synchronization locks.
Code Example: Phased Simulation
The following example demonstrates three worker threads working through multiple synchronized rounds:
import threading
import time
import random
def phase_callback():
print("--- Phase synchronization complete. Moving to next round. ---\n")
ROUNDS = 3
NUM_WORKERS = 3
barrier = threading.Barrier(NUM_WORKERS, action=phase_callback)
def worker(worker_id):
for step in range(ROUNDS):
# Simulate varying execution time
time.sleep(random.uniform(0.1, 0.3))
print(f"Worker {worker_id} completed phase {step}")
# Wait for all workers to finish the current phase
barrier.wait()
threads = [threading.Thread(target=worker, args=(i,)) for i in range(NUM_WORKERS)]
for t in threads:
t.start()
for t in threads:
t.join()In every iteration of the loop, no worker can start step
N + 1 until all workers have finished step
N.
Handling Timeouts and Broken Barriers
A potential hazard in barrier synchronization is a stalled or failed
thread, which can leave other threads blocked indefinitely. To mitigate
this, wait() accepts a timeout argument:
try:
barrier.wait(timeout=2.0)
except threading.BrokenBarrierError:
print("A thread timed out or the barrier was broken.")If any thread times out while waiting, or if
barrier.reset() is invoked while threads are waiting, the
barrier transitions into a "broken" state and raises a
threading.BrokenBarrierError to all current and subsequent
waiting threads. The barrier.broken attribute can be
inspected to check for this state, and calling
barrier.abort() forces the barrier into a broken state
manually to trigger clean cancellation across all coordinated
threads.