authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-15 00:27:26+00:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-01-15 05:28:11+01:00
log4f16e80ceadc19c50c5be67dd9cc2c2f9aa4beb8
treec9e918e2a521a053472970907730921cc866c356
parentc518593e9793a2aed4e0173348aff2cbef58b717

std: halve the number of mutexes per mutex

On NetBSD and Illumos, we were using the `std.Thread.Futex`-based implementation of `std.Thread.Mutex`. But since futex is not a primitive on these targets, the implementation of `std.Thread.Futex` was based on pthread primitives, including `pthread_mutex_t`. This had the amusing consequence that locking a contended mutex on NetBSD would actually perform 2 mutex locks, 2 mutex unlocks, and 1 condition wait; likewise, unlocking a contended mutex would perform 2 mutex locks, 2 mutex unlocks, and 1 condition signal. Having read some cutting-edge studies, I have concluded that this is a slightly suboptimal approach. Instead, let's just use pthread mutexes directly in this case; that's an obviously better idea. In the future, I think we can probably entirely remove our usages of pthread sync primitives---no platform actually treats them as the base primitives. Of the platforms which std has any meaningful support for today, most support futexes, and the exceptions (NetBSD and Illumos) support a thread parking API. We can implement futex and/or mutex on top of thread parking and drop the pthread dependency entirely.

1 files changed, 55 insertions(+), 8 deletions(-)

lib/std/Thread/Mutex.zig+55-8
...@@ -45,14 +45,30 @@ const Impl = if (builtin.mode == .Debug and !builtin.single_threaded)...@@ -45,14 +45,30 @@ const Impl = if (builtin.mode == .Debug and !builtin.single_threaded)
45else45else
46 ReleaseImpl;46 ReleaseImpl;
4747
48const ReleaseImpl = if (builtin.single_threaded)48const ReleaseImpl = Impl: {
49 SingleThreadedImpl49 if (builtin.single_threaded) break :Impl SingleThreadedImpl;
50else if (builtin.os.tag == .windows)50 if (builtin.os.tag == .windows) break :Impl WindowsImpl;
51 WindowsImpl51 if (builtin.os.tag.isDarwin()) break :Impl DarwinImpl;
52else if (builtin.os.tag.isDarwin())52
53 DarwinImpl53 if (builtin.target.os.tag == .linux or
54else54 builtin.target.os.tag == .freebsd or
55 FutexImpl;55 builtin.target.os.tag == .openbsd or
56 builtin.target.os.tag == .dragonfly or
57 builtin.target.cpu.arch.isWasm())
58 {
59 // Futex is the system's synchronization primitive; use that.
60 break :Impl FutexImpl;
61 }
62
63 if (std.Thread.use_pthreads) {
64 // This system doesn't have a futex primitive, so `std.Thread.Futex` is using `PosixImpl`,
65 // which implements futex *on top of* pthread mutexes and conditions. Therefore, instead
66 // of going through that long inefficient path, just use pthread mutex directly.
67 break :Impl PosixImpl;
68 }
69
70 break :Impl FutexImpl;
71};
5672
57const DebugImpl = struct {73const DebugImpl = struct {
58 locking_thread: std.atomic.Value(Thread.Id) = std.atomic.Value(Thread.Id).init(0), // 0 means it's not locked.74 locking_thread: std.atomic.Value(Thread.Id) = std.atomic.Value(Thread.Id).init(0), // 0 means it's not locked.
...@@ -208,6 +224,37 @@ const FutexImpl = struct {...@@ -208,6 +224,37 @@ const FutexImpl = struct {
208 }224 }
209};225};
210226
227const PosixImpl = struct {
228 mutex: std.c.pthread_mutex_t = .{},
229
230 fn tryLock(impl: *PosixImpl) bool {
231 switch (std.c.pthread_mutex_trylock(&impl.mutex)) {
232 .SUCCESS => return true,
233 .BUSY => return false,
234 .INVAL => unreachable, // mutex is initialized correctly
235 else => unreachable,
236 }
237 }
238
239 fn lock(impl: *PosixImpl) void {
240 switch (std.c.pthread_mutex_lock(&impl.mutex)) {
241 .SUCCESS => return,
242 .INVAL => unreachable, // mutex is initialized correctly
243 .DEADLK => unreachable, // not an error checking mutex
244 else => unreachable,
245 }
246 }
247
248 fn unlock(impl: *PosixImpl) void {
249 switch (std.c.pthread_mutex_unlock(&impl.mutex)) {
250 .SUCCESS => return,
251 .INVAL => unreachable, // mutex is initialized correctly
252 .PERM => unreachable, // not an error checking mutex
253 else => unreachable,
254 }
255 }
256};
257
211test "smoke test" {258test "smoke test" {
212 var mutex = Mutex{};259 var mutex = Mutex{};
213260