zig/lib/std/Thread/Mutex.zig
LemonBoy 566adc2510 std: Swap arguments in Thread.spawn
Beside the new order being consistent with the ThreadPool API and making
more sense, this shuffling allows to write the context argument type in
terms of the startFn arguments, reducing the use of anytype (eg. less
explicit casts when using comptime_int parameters, yay).

Sorry for the breakage.

Closes #8082
2021-02-28 14:03:19 +01:00

320 lines
8.8 KiB
Zig

// SPDX-License-Identifier: MIT
// Copyright (c) 2015-2021 Zig Contributors
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
//! Lock may be held only once. If the same thread tries to acquire
//! the same mutex twice, it deadlocks. This type supports static
//! initialization and is at most `@sizeOf(usize)` in size. When an
//! application is built in single threaded release mode, all the
//! functions are no-ops. In single threaded debug mode, there is
//! deadlock detection.
//!
//! Example usage:
//! var m = Mutex{};
//!
//! const lock = m.acquire();
//! defer lock.release();
//! ... critical code
//!
//! Non-blocking:
//! if (m.tryAcquire) |lock| {
//! defer lock.release();
//! // ... critical section
//! } else {
//! // ... lock not acquired
//! }
impl: Impl = .{},
const Mutex = @This();
const std = @import("../std.zig");
const builtin = std.builtin;
const os = std.os;
const assert = std.debug.assert;
const windows = os.windows;
const linux = os.linux;
const testing = std.testing;
const StaticResetEvent = std.thread.StaticResetEvent;
/// Try to acquire the mutex without blocking. Returns `null` if the mutex is
/// unavailable. Otherwise returns `Held`. Call `release` on `Held`.
pub fn tryAcquire(m: *Mutex) ?Impl.Held {
return m.impl.tryAcquire();
}
/// Acquire the mutex. Deadlocks if the mutex is already
/// held by the calling thread.
pub fn acquire(m: *Mutex) Impl.Held {
return m.impl.acquire();
}
const Impl = if (builtin.single_threaded)
Dummy
else if (builtin.os.tag == .windows)
WindowsMutex
else if (std.Thread.use_pthreads)
PthreadMutex
else
AtomicMutex;
pub const AtomicMutex = struct {
state: State = .unlocked,
const State = enum(i32) {
unlocked,
locked,
waiting,
};
pub const Held = struct {
mutex: *AtomicMutex,
pub fn release(held: Held) void {
switch (@atomicRmw(State, &held.mutex.state, .Xchg, .unlocked, .Release)) {
.unlocked => unreachable,
.locked => {},
.waiting => held.mutex.unlockSlow(),
}
}
};
pub fn tryAcquire(m: *AtomicMutex) ?Held {
if (@cmpxchgStrong(
State,
&m.state,
.unlocked,
.locked,
.Acquire,
.Monotonic,
) == null) {
return Held{ .mutex = m };
} else {
return null;
}
}
pub fn acquire(m: *AtomicMutex) Held {
switch (@atomicRmw(State, &m.state, .Xchg, .locked, .Acquire)) {
.unlocked => {},
else => |s| m.lockSlow(s),
}
return Held{ .mutex = m };
}
fn lockSlow(m: *AtomicMutex, current_state: State) void {
@setCold(true);
var new_state = current_state;
var spin: u8 = 0;
while (spin < 100) : (spin += 1) {
const state = @cmpxchgWeak(
State,
&m.state,
.unlocked,
new_state,
.Acquire,
.Monotonic,
) orelse return;
switch (state) {
.unlocked => {},
.locked => {},
.waiting => break,
}
var iter = std.math.min(32, spin + 1);
while (iter > 0) : (iter -= 1)
std.Thread.spinLoopHint();
}
new_state = .waiting;
while (true) {
switch (@atomicRmw(State, &m.state, .Xchg, new_state, .Acquire)) {
.unlocked => return,
else => {},
}
switch (std.Target.current.os.tag) {
.linux => {
switch (linux.getErrno(linux.futex_wait(
@ptrCast(*const i32, &m.state),
linux.FUTEX_PRIVATE_FLAG | linux.FUTEX_WAIT,
@enumToInt(new_state),
null,
))) {
0 => {},
std.os.EINTR => {},
std.os.EAGAIN => {},
else => unreachable,
}
},
else => std.Thread.spinLoopHint(),
}
}
}
fn unlockSlow(m: *AtomicMutex) void {
@setCold(true);
switch (std.Target.current.os.tag) {
.linux => {
switch (linux.getErrno(linux.futex_wake(
@ptrCast(*const i32, &m.state),
linux.FUTEX_PRIVATE_FLAG | linux.FUTEX_WAKE,
1,
))) {
0 => {},
std.os.EFAULT => {},
else => unreachable,
}
},
else => {},
}
}
};
pub const PthreadMutex = struct {
pthread_mutex: std.c.pthread_mutex_t = .{},
pub const Held = struct {
mutex: *PthreadMutex,
pub fn release(held: Held) void {
switch (std.c.pthread_mutex_unlock(&held.mutex.pthread_mutex)) {
0 => return,
std.c.EINVAL => unreachable,
std.c.EAGAIN => unreachable,
std.c.EPERM => unreachable,
else => unreachable,
}
}
};
/// Try to acquire the mutex without blocking. Returns null if
/// the mutex is unavailable. Otherwise returns Held. Call
/// release on Held.
pub fn tryAcquire(m: *PthreadMutex) ?Held {
if (std.c.pthread_mutex_trylock(&m.pthread_mutex) == 0) {
return Held{ .mutex = m };
} else {
return null;
}
}
/// Acquire the mutex. Will deadlock if the mutex is already
/// held by the calling thread.
pub fn acquire(m: *PthreadMutex) Held {
switch (std.c.pthread_mutex_lock(&m.pthread_mutex)) {
0 => return Held{ .mutex = m },
std.c.EINVAL => unreachable,
std.c.EBUSY => unreachable,
std.c.EAGAIN => unreachable,
std.c.EDEADLK => unreachable,
std.c.EPERM => unreachable,
else => unreachable,
}
}
};
/// This has the sematics as `Mutex`, however it does not actually do any
/// synchronization. Operations are safety-checked no-ops.
pub const Dummy = struct {
lock: @TypeOf(lock_init) = lock_init,
const lock_init = if (std.debug.runtime_safety) false else {};
pub const Held = struct {
mutex: *Dummy,
pub fn release(held: Held) void {
if (std.debug.runtime_safety) {
held.mutex.lock = false;
}
}
};
/// Try to acquire the mutex without blocking. Returns null if
/// the mutex is unavailable. Otherwise returns Held. Call
/// release on Held.
pub fn tryAcquire(m: *Dummy) ?Held {
if (std.debug.runtime_safety) {
if (m.lock) return null;
m.lock = true;
}
return Held{ .mutex = m };
}
/// Acquire the mutex. Will deadlock if the mutex is already
/// held by the calling thread.
pub fn acquire(m: *Dummy) Held {
return m.tryAcquire() orelse @panic("deadlock detected");
}
};
const WindowsMutex = struct {
srwlock: windows.SRWLOCK = windows.SRWLOCK_INIT,
pub const Held = struct {
mutex: *WindowsMutex,
pub fn release(held: Held) void {
windows.kernel32.ReleaseSRWLockExclusive(&held.mutex.srwlock);
}
};
pub fn tryAcquire(m: *WindowsMutex) ?Held {
if (windows.kernel32.TryAcquireSRWLockExclusive(&m.srwlock) != windows.FALSE) {
return Held{ .mutex = m };
} else {
return null;
}
}
pub fn acquire(m: *WindowsMutex) Held {
windows.kernel32.AcquireSRWLockExclusive(&m.srwlock);
return Held{ .mutex = m };
}
};
const TestContext = struct {
mutex: *Mutex,
data: i128,
const incr_count = 10000;
};
test "basic usage" {
var mutex = Mutex{};
var context = TestContext{
.mutex = &mutex,
.data = 0,
};
if (builtin.single_threaded) {
worker(&context);
testing.expect(context.data == TestContext.incr_count);
} else {
const thread_count = 10;
var threads: [thread_count]*std.Thread = undefined;
for (threads) |*t| {
t.* = try std.Thread.spawn(worker, &context);
}
for (threads) |t|
t.wait();
testing.expect(context.data == thread_count * TestContext.incr_count);
}
}
fn worker(ctx: *TestContext) void {
var i: usize = 0;
while (i != TestContext.incr_count) : (i += 1) {
const held = ctx.mutex.acquire();
defer held.release();
ctx.data += 1;
}
}