organize std lib concurrency primitives and add RwLock
* move concurrency primitives that always operate on kernel threads to the std.Thread namespace * remove std.SpinLock. Nobody should use this in a non-freestanding environment; the other primitives are always preferable. In freestanding, it will be necessary to put custom spin logic in there, so there are no use cases for a std lib version. * move some std lib files to the top level fields convention * add std.Thread.spinLoopHint * add std.Thread.Condition * add std.Thread.Semaphore * new implementation of std.Thread.Mutex for Windows and non-pthreads Linux * add std.Thread.RwLock Implementations provided by @kprotty
This commit is contained in:
228
lib/std/Thread/AutoResetEvent.zig
Normal file
228
lib/std/Thread/AutoResetEvent.zig
Normal file
@@ -0,0 +1,228 @@
|
||||
// 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.
|
||||
|
||||
//! Similar to `StaticResetEvent` but on `set()` it also (atomically) does `reset()`.
|
||||
//! Unlike StaticResetEvent, `wait()` can only be called by one thread (MPSC-like).
|
||||
//!
|
||||
//! AutoResetEvent has 3 possible states:
|
||||
//! - UNSET: the AutoResetEvent is currently unset
|
||||
//! - SET: the AutoResetEvent was notified before a wait() was called
|
||||
//! - <StaticResetEvent pointer>: there is an active waiter waiting for a notification.
|
||||
//!
|
||||
//! When attempting to wait:
|
||||
//! if the event is unset, it registers a ResetEvent pointer to be notified when the event is set
|
||||
//! if the event is already set, then it consumes the notification and resets the event.
|
||||
//!
|
||||
//! When attempting to notify:
|
||||
//! if the event is unset, then we set the event
|
||||
//! if theres a waiting ResetEvent, then we unset the event and notify the ResetEvent
|
||||
//!
|
||||
//! This ensures that the event is automatically reset after a wait() has been issued
|
||||
//! and avoids the race condition when using StaticResetEvent in the following scenario:
|
||||
//! thread 1 | thread 2
|
||||
//! StaticResetEvent.wait() |
|
||||
//! | StaticResetEvent.set()
|
||||
//! | StaticResetEvent.set()
|
||||
//! StaticResetEvent.reset() |
|
||||
//! StaticResetEvent.wait() | (missed the second .set() notification above)
|
||||
|
||||
state: usize = UNSET,
|
||||
|
||||
const std = @import("../std.zig");
|
||||
const builtin = @import("builtin");
|
||||
const testing = std.testing;
|
||||
const assert = std.debug.assert;
|
||||
const StaticResetEvent = std.Thread.StaticResetEvent;
|
||||
const AutoResetEvent = @This();
|
||||
|
||||
const UNSET = 0;
|
||||
const SET = 1;
|
||||
|
||||
/// the minimum alignment for the `*StaticResetEvent` created by wait*()
|
||||
const event_align = std.math.max(@alignOf(StaticResetEvent), 2);
|
||||
|
||||
pub fn wait(self: *AutoResetEvent) void {
|
||||
self.waitFor(null) catch unreachable;
|
||||
}
|
||||
|
||||
pub fn timedWait(self: *AutoResetEvent, timeout: u64) error{TimedOut}!void {
|
||||
return self.waitFor(timeout);
|
||||
}
|
||||
|
||||
fn waitFor(self: *AutoResetEvent, timeout: ?u64) error{TimedOut}!void {
|
||||
// lazily initialized StaticResetEvent
|
||||
var reset_event: StaticResetEvent align(event_align) = undefined;
|
||||
var has_reset_event = false;
|
||||
|
||||
var state = @atomicLoad(usize, &self.state, .SeqCst);
|
||||
while (true) {
|
||||
// consume a notification if there is any
|
||||
if (state == SET) {
|
||||
@atomicStore(usize, &self.state, UNSET, .SeqCst);
|
||||
return;
|
||||
}
|
||||
|
||||
// check if theres currently a pending ResetEvent pointer already registered
|
||||
if (state != UNSET) {
|
||||
unreachable; // multiple waiting threads on the same AutoResetEvent
|
||||
}
|
||||
|
||||
// lazily initialize the ResetEvent if it hasn't been already
|
||||
if (!has_reset_event) {
|
||||
has_reset_event = true;
|
||||
reset_event = .{};
|
||||
}
|
||||
|
||||
// Since the AutoResetEvent currently isnt set,
|
||||
// try to register our ResetEvent on it to wait
|
||||
// for a set() call from another thread.
|
||||
if (@cmpxchgWeak(
|
||||
usize,
|
||||
&self.state,
|
||||
UNSET,
|
||||
@ptrToInt(&reset_event),
|
||||
.SeqCst,
|
||||
.SeqCst,
|
||||
)) |new_state| {
|
||||
state = new_state;
|
||||
continue;
|
||||
}
|
||||
|
||||
// if no timeout was specified, then just wait forever
|
||||
const timeout_ns = timeout orelse {
|
||||
reset_event.wait();
|
||||
return;
|
||||
};
|
||||
|
||||
// wait with a timeout and return if signalled via set()
|
||||
switch (reset_event.timedWait(timeout_ns)) {
|
||||
.event_set => return,
|
||||
.timed_out => {},
|
||||
}
|
||||
|
||||
// If we timed out, we need to transition the AutoResetEvent back to UNSET.
|
||||
// If we don't, then when we return, a set() thread could observe a pointer to an invalid ResetEvent.
|
||||
state = @cmpxchgStrong(
|
||||
usize,
|
||||
&self.state,
|
||||
@ptrToInt(&reset_event),
|
||||
UNSET,
|
||||
.SeqCst,
|
||||
.SeqCst,
|
||||
) orelse return error.TimedOut;
|
||||
|
||||
// We didn't manage to unregister ourselves from the state.
|
||||
if (state == SET) {
|
||||
unreachable; // AutoResetEvent notified without waking up the waiting thread
|
||||
} else if (state != UNSET) {
|
||||
unreachable; // multiple waiting threads on the same AutoResetEvent observed when timing out
|
||||
}
|
||||
|
||||
// This menas a set() thread saw our ResetEvent pointer, acquired it, and is trying to wake it up.
|
||||
// We need to wait for it to wake up our ResetEvent before we can return and invalidate it.
|
||||
// We don't return error.TimedOut here as it technically notified us while we were "timing out".
|
||||
reset_event.wait();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(self: *AutoResetEvent) void {
|
||||
var state = @atomicLoad(usize, &self.state, .SeqCst);
|
||||
while (true) {
|
||||
// If the AutoResetEvent is already set, there is nothing else left to do
|
||||
if (state == SET) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the AutoResetEvent isn't set,
|
||||
// then try to leave a notification for the wait() thread that we set() it.
|
||||
if (state == UNSET) {
|
||||
state = @cmpxchgWeak(
|
||||
usize,
|
||||
&self.state,
|
||||
UNSET,
|
||||
SET,
|
||||
.SeqCst,
|
||||
.SeqCst,
|
||||
) orelse return;
|
||||
continue;
|
||||
}
|
||||
|
||||
// There is a ResetEvent pointer registered on the AutoResetEvent event thats waiting.
|
||||
// Try to acquire ownership of it so that we can wake it up.
|
||||
// This also resets the AutoResetEvent so that there is no race condition as defined above.
|
||||
if (@cmpxchgWeak(
|
||||
usize,
|
||||
&self.state,
|
||||
state,
|
||||
UNSET,
|
||||
.SeqCst,
|
||||
.SeqCst,
|
||||
)) |new_state| {
|
||||
state = new_state;
|
||||
continue;
|
||||
}
|
||||
|
||||
const reset_event = @intToPtr(*align(event_align) StaticResetEvent, state);
|
||||
reset_event.set();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
test "basic usage" {
|
||||
// test local code paths
|
||||
{
|
||||
var event = AutoResetEvent{};
|
||||
testing.expectError(error.TimedOut, event.timedWait(1));
|
||||
event.set();
|
||||
event.wait();
|
||||
}
|
||||
|
||||
// test cross-thread signaling
|
||||
if (builtin.single_threaded)
|
||||
return;
|
||||
|
||||
const Context = struct {
|
||||
value: u128 = 0,
|
||||
in: AutoResetEvent = AutoResetEvent{},
|
||||
out: AutoResetEvent = AutoResetEvent{},
|
||||
|
||||
const Self = @This();
|
||||
|
||||
fn sender(self: *Self) void {
|
||||
testing.expect(self.value == 0);
|
||||
self.value = 1;
|
||||
self.out.set();
|
||||
|
||||
self.in.wait();
|
||||
testing.expect(self.value == 2);
|
||||
self.value = 3;
|
||||
self.out.set();
|
||||
|
||||
self.in.wait();
|
||||
testing.expect(self.value == 4);
|
||||
}
|
||||
|
||||
fn receiver(self: *Self) void {
|
||||
self.out.wait();
|
||||
testing.expect(self.value == 1);
|
||||
self.value = 2;
|
||||
self.in.set();
|
||||
|
||||
self.out.wait();
|
||||
testing.expect(self.value == 3);
|
||||
self.value = 4;
|
||||
self.in.set();
|
||||
}
|
||||
};
|
||||
|
||||
var context = Context{};
|
||||
const send_thread = try std.Thread.spawn(&context, Context.sender);
|
||||
const recv_thread = try std.Thread.spawn(&context, Context.receiver);
|
||||
|
||||
send_thread.wait();
|
||||
recv_thread.wait();
|
||||
}
|
||||
Reference in New Issue
Block a user