I ended up implementing the following code:
File: Barrier.zig
//! A thread barrier is a mechanism for thread synchronization.
//! Blocks threads until a certain number of threads has reached the barrier,
//! after which all threads are unblocked and the barrier is reset.
//!
//! Based on http://byronlai.com/jekyll/update/2015/12/26/barrier.html
const std = @import("std");
const Self = @This();
mutex: std.Thread.Mutex,
cond: std.Thread.Condition,
required_thread_count: usize,
current_thread_count: usize,
cycle: usize,
pub fn init(
/// Number of threads that must enter the barrier before it is lifted.
thread_count: usize,
) Self {
return .{
.mutex = .{},
.cond = .{},
.required_thread_count = thread_count,
.current_thread_count = 0,
.cycle = 0,
};
}
/// Wait at the barrier. If this is the final thread, lift the barrier instead.
pub fn wait(self: *Self) void {
self.mutex.lock();
defer self.mutex.unlock();
self.current_thread_count += 1;
if (self.current_thread_count == self.required_thread_count) {
// All threads have arrived at the barrier.
self.cycle += 1;
self.current_thread_count = 0;
self.cond.broadcast();
} else {
// Wait for other threads to arrive...
const my_cycle: usize = self.cycle;
// Threads may sporadically wake up. This loop prevents them from leaving the barrier too early.
while (self.cycle == my_cycle)
self.cond.wait(&self.mutex);
}
}
/// Pass through the barrier without waiting. If this is the final thread, also lift the barrier.
pub fn passthrough(self: *Self) void {
self.mutex.lock();
defer self.mutex.unlock();
self.current_thread_count += 1;
if (self.current_thread_count == self.required_thread_count) {
// All threads have arrived at the barrier.
self.cycle += 1;
self.current_thread_count = 0;
self.cond.broadcast();
}
}