| author | |
| committer | |
| log | 963ac60918b39cafdda3cb99eff4cd9d20edd839 |
| tree | 8b480e6c1c01c11758f47b1126c53e75a339d1a1 |
| parent | daef82d06fd6b30d2cab7f5a6723cf2e3c7b48c6 |
| signature |
* Thread: minor cleanups
* Thread: rewrite Mutex
* Thread: introduce Futex.Deadline
* Thread: Condition rewrite + cleanup
* Mutex: optimize lock fast path
* Condition: more docs
* Thread: more mutex + condition docs
* Thread: remove broken Condition test
* Thread: zig fmt
* address review comments + fix Thread.DummyMutex in GPA
* Atomic: disable bitRmw x86 inline asm for stage2
* GPA: typo mutex_init
* Thread: remove noalias on stuff
* Thread: comment typos + clarifications7 files changed, 810 insertions(+), 640 deletions(-)
lib/std/Thread.zig+1-26| ... | @@ -459,9 +459,8 @@ const UnsupportedImpl = struct { | ... | @@ -459,9 +459,8 @@ const UnsupportedImpl = struct { |
| 459 | } | 459 | } |
| 460 | 460 | ||
| 461 | fn unsupported(unusued: anytype) noreturn { | 461 | fn unsupported(unusued: anytype) noreturn { |
| 462 | @compileLog("Unsupported operating system", target.os.tag); | ||
| 463 | _ = unusued; | 462 | _ = unusued; |
| 464 | unreachable; | 463 | @compileError("Unsupported operating system " ++ @tagName(target.os.tag)); |
| 465 | } | 464 | } |
| 466 | }; | 465 | }; |
| 467 | 466 | ||
| ... | @@ -1188,27 +1187,3 @@ test "Thread.detach" { | ... | @@ -1188,27 +1187,3 @@ test "Thread.detach" { |
| 1188 | event.wait(); | 1187 | event.wait(); |
| 1189 | try std.testing.expectEqual(value, 1); | 1188 | try std.testing.expectEqual(value, 1); |
| 1190 | } | 1189 | } |
| 1191 | |||
| 1192 | fn testWaitForSignal(mutex: *Mutex, cond: *Condition) void { | ||
| 1193 | mutex.lock(); | ||
| 1194 | defer mutex.unlock(); | ||
| 1195 | cond.signal(); | ||
| 1196 | cond.wait(mutex); | ||
| 1197 | } | ||
| 1198 | |||
| 1199 | test "Condition.signal" { | ||
| 1200 | if (builtin.single_threaded) return error.SkipZigTest; | ||
| 1201 | |||
| 1202 | var mutex = Mutex{}; | ||
| 1203 | var cond = Condition{}; | ||
| 1204 | |||
| 1205 | var thread: Thread = undefined; | ||
| 1206 | { | ||
| 1207 | mutex.lock(); | ||
| 1208 | defer mutex.unlock(); | ||
| 1209 | thread = try Thread.spawn(.{}, testWaitForSignal, .{ &mutex, &cond }); | ||
| 1210 | cond.wait(&mutex); | ||
| 1211 | cond.signal(); | ||
| 1212 | } | ||
| 1213 | thread.join(); | ||
| 1214 | } |
lib/std/Thread/Condition.zig+435-308| ... | @@ -1,411 +1,538 @@ | ... | @@ -1,411 +1,538 @@ |
| 1 | //! A condition provides a way for a kernel thread to block until it is signaled | 1 | //! Condition variables are used with a Mutex to efficiently wait for an arbitrary condition to occur. |
| 2 | //! to wake up. Spurious wakeups are possible. | 2 | //! It does this by atomically unlocking the mutex, blocking the thread until notified, and finally re-locking the mutex. |
| 3 | //! This API supports static initialization and does not require deinitialization. | 3 | //! Condition can be statically initialized and is at most `@sizeOf(u64)` large. |
| 4 | 4 | //! | |
| 5 | impl: Impl = .{}, | 5 | //! Example: |
| 6 | //! ``` | ||
| 7 | //! var m = Mutex{}; | ||
| 8 | //! var c = Condition{}; | ||
| 9 | //! var predicate = false; | ||
| 10 | //! | ||
| 11 | //! fn consumer() void { | ||
| 12 | //! m.lock(); | ||
| 13 | //! defer m.unlock(); | ||
| 14 | //! | ||
| 15 | //! while (!predicate) { | ||
| 16 | //! c.wait(&mutex); | ||
| 17 | //! } | ||
| 18 | //! } | ||
| 19 | //! | ||
| 20 | //! fn producer() void { | ||
| 21 | //! m.lock(); | ||
| 22 | //! defer m.unlock(); | ||
| 23 | //! | ||
| 24 | //! predicate = true; | ||
| 25 | //! c.signal(); | ||
| 26 | //! } | ||
| 27 | //! | ||
| 28 | //! const thread = try std.Thread.spawn(.{}, producer, .{}); | ||
| 29 | //! consumer(); | ||
| 30 | //! thread.join(); | ||
| 31 | //! ``` | ||
| 32 | //! | ||
| 33 | //! Note that condition variables can only reliably unblock threads that are sequenced before them using the same Mutex. | ||
| 34 | //! This means that the following is allowed to deadlock: | ||
| 35 | //! ``` | ||
| 36 | //! thread-1: mutex.lock() | ||
| 37 | //! thread-1: condition.wait(&mutex) | ||
| 38 | //! | ||
| 39 | //! thread-2: // mutex.lock() (without this, the following signal may not see the waiting thread-1) | ||
| 40 | //! thread-2: // mutex.unlock() (this is optional for correctness once locked above, as signal can be called without holding the mutex) | ||
| 41 | //! thread-2: condition.signal() | ||
| 42 | //! ``` | ||
| 6 | 43 | ||
| 7 | const std = @import("../std.zig"); | 44 | const std = @import("../std.zig"); |
| 8 | const builtin = @import("builtin"); | 45 | const builtin = @import("builtin"); |
| 9 | const Condition = @This(); | 46 | const Condition = @This(); |
| 10 | const windows = std.os.windows; | ||
| 11 | const linux = std.os.linux; | ||
| 12 | const Mutex = std.Thread.Mutex; | 47 | const Mutex = std.Thread.Mutex; |
| 48 | |||
| 49 | const os = std.os; | ||
| 13 | const assert = std.debug.assert; | 50 | const assert = std.debug.assert; |
| 14 | const testing = std.testing; | 51 | const testing = std.testing; |
| 52 | const Atomic = std.atomic.Atomic; | ||
| 53 | const Futex = std.Thread.Futex; | ||
| 54 | |||
| 55 | impl: Impl = .{}, | ||
| 15 | 56 | ||
| 16 | pub fn wait(cond: *Condition, mutex: *Mutex) void { | 57 | /// Atomically releases the Mutex, blocks the caller thread, then re-acquires the Mutex on return. |
| 17 | cond.impl.wait(mutex); | 58 | /// "Atomically" here refers to accesses done on the Condition after acquiring the Mutex. |
| 59 | /// | ||
| 60 | /// The Mutex must be locked by the caller's thread when this function is called. | ||
| 61 | /// A Mutex can have multiple Conditions waiting with it concurrently, but not the opposite. | ||
| 62 | /// It is undefined behavior for multiple threads to wait ith different mutexes using the same Condition concurrently. | ||
| 63 | /// Once threads have finished waiting with one Mutex, the Condition can be used to wait with another Mutex. | ||
| 64 | /// | ||
| 65 | /// A blocking call to wait() is unblocked from one of the following conditions: | ||
| 66 | /// - a spurious ("at random") wake up occurs | ||
| 67 | /// - a future call to `signal()` or `broadcast()` which has acquired the Mutex and is sequenced after this `wait()`. | ||
| 68 | /// | ||
| 69 | /// Given wait() can be interrupted spuriously, the blocking condition should be checked continuously | ||
| 70 | /// irrespective of any notifications from `signal()` or `broadcast()`. | ||
| 71 | pub fn wait(self: *Condition, mutex: *Mutex) void { | ||
| 72 | self.impl.wait(mutex, null) catch |err| switch (err) { | ||
| 73 | error.Timeout => unreachable, // no timeout provided so we shouldn't have timed-out | ||
| 74 | }; | ||
| 18 | } | 75 | } |
| 19 | 76 | ||
| 20 | pub fn timedWait(cond: *Condition, mutex: *Mutex, timeout_ns: u64) error{TimedOut}!void { | 77 | /// Atomically releases the Mutex, blocks the caller thread, then re-acquires the Mutex on return. |
| 21 | try cond.impl.timedWait(mutex, timeout_ns); | 78 | /// "Atomically" here refers to accesses done on the Condition after acquiring the Mutex. |
| 79 | /// | ||
| 80 | /// The Mutex must be locked by the caller's thread when this function is called. | ||
| 81 | /// A Mutex can have multiple Conditions waiting with it concurrently, but not the opposite. | ||
| 82 | /// It is undefined behavior for multiple threads to wait ith different mutexes using the same Condition concurrently. | ||
| 83 | /// Once threads have finished waiting with one Mutex, the Condition can be used to wait with another Mutex. | ||
| 84 | /// | ||
| 85 | /// A blocking call to `timedWait()` is unblocked from one of the following conditions: | ||
| 86 | /// - a spurious ("at random") wake occurs | ||
| 87 | /// - the caller was blocked for around `timeout_ns` nanoseconds, in which `error.Timeout` is returned. | ||
| 88 | /// - a future call to `signal()` or `broadcast()` which has acquired the Mutex and is sequenced after this `timedWait()`. | ||
| 89 | /// | ||
| 90 | /// Given `timedWait()` can be interrupted spuriously, the blocking condition should be checked continuously | ||
| 91 | /// irrespective of any notifications from `signal()` or `broadcast()`. | ||
| 92 | pub fn timedWait(self: *Condition, mutex: *Mutex, timeout_ns: u64) error{Timeout}!void { | ||
| 93 | return self.impl.wait(mutex, timeout_ns); | ||
| 22 | } | 94 | } |
| 23 | 95 | ||
| 24 | pub fn signal(cond: *Condition) void { | 96 | /// Unblocks at least one thread blocked in a call to `wait()` or `timedWait()` with a given Mutex. |
| 25 | cond.impl.signal(); | 97 | /// The blocked thread must be sequenced before this call with respect to acquiring the same Mutex in order to be observable for unblocking. |
| 98 | /// `signal()` can be called with or without the relevant Mutex being acquired and have no "effect" if there's no observable blocked threads. | ||
| 99 | pub fn signal(self: *Condition) void { | ||
| 100 | self.impl.wake(.one); | ||
| 26 | } | 101 | } |
| 27 | 102 | ||
| 28 | pub fn broadcast(cond: *Condition) void { | 103 | /// Unblocks all threads currently blocked in a call to `wait()` or `timedWait()` with a given Mutex. |
| 29 | cond.impl.broadcast(); | 104 | /// The blocked threads must be sequenced before this call with respect to acquiring the same Mutex in order to be observable for unblocking. |
| 105 | /// `broadcast()` can be called with or without the relevant Mutex being acquired and have no "effect" if there's no observable blocked threads. | ||
| 106 | pub fn broadcast(self: *Condition) void { | ||
| 107 | self.impl.wake(.all); | ||
| 30 | } | 108 | } |
| 31 | 109 | ||
| 32 | const Impl = if (builtin.single_threaded) | 110 | const Impl = if (builtin.single_threaded) |
| 33 | SingleThreadedCondition | 111 | SingleThreadedImpl |
| 34 | else if (builtin.os.tag == .windows) | 112 | else if (builtin.os.tag == .windows) |
| 35 | WindowsCondition | 113 | WindowsImpl |
| 36 | else if (std.Thread.use_pthreads) | ||
| 37 | PthreadCondition | ||
| 38 | else | 114 | else |
| 39 | AtomicCondition; | 115 | FutexImpl; |
| 40 | 116 | ||
| 41 | pub const SingleThreadedCondition = struct { | 117 | const Notify = enum { |
| 42 | pub fn wait(cond: *SingleThreadedCondition, mutex: *Mutex) void { | 118 | one, // wake up only one thread |
| 43 | _ = cond; | 119 | all, // wake up all threads |
| 44 | _ = mutex; | 120 | }; |
| 45 | unreachable; // deadlock detected | ||
| 46 | } | ||
| 47 | 121 | ||
| 48 | pub fn timedWait(cond: *SingleThreadedCondition, mutex: *Mutex, timeout_ns: u64) error{TimedOut}!void { | 122 | const SingleThreadedImpl = struct { |
| 49 | _ = cond; | 123 | fn wait(self: *Impl, mutex: *Mutex, timeout: ?u64) error{Timeout}!void { |
| 124 | _ = self; | ||
| 50 | _ = mutex; | 125 | _ = mutex; |
| 51 | _ = timeout_ns; | ||
| 52 | std.time.sleep(timeout_ns); | ||
| 53 | return error.TimedOut; | ||
| 54 | } | ||
| 55 | 126 | ||
| 56 | pub fn signal(cond: *SingleThreadedCondition) void { | 127 | // There are no other threads to wake us up. |
| 57 | _ = cond; | 128 | // So if we wait without a timeout we would never wake up. |
| 129 | const timeout_ns = timeout orelse { | ||
| 130 | unreachable; // deadlock detected | ||
| 131 | }; | ||
| 132 | |||
| 133 | std.time.sleep(timeout_ns); | ||
| 134 | return error.Timeout; | ||
| 58 | } | 135 | } |
| 59 | 136 | ||
| 60 | pub fn broadcast(cond: *SingleThreadedCondition) void { | 137 | fn wake(self: *Impl, comptime notify: Notify) void { |
| 61 | _ = cond; | 138 | // There are no other threads to wake up. |
| 139 | _ = self; | ||
| 140 | _ = notify; | ||
| 62 | } | 141 | } |
| 63 | }; | 142 | }; |
| 64 | 143 | ||
| 65 | pub const WindowsCondition = struct { | 144 | const WindowsImpl = struct { |
| 66 | cond: windows.CONDITION_VARIABLE = windows.CONDITION_VARIABLE_INIT, | 145 | condition: os.windows.CONDITION_VARIABLE = .{}, |
| 67 | 146 | ||
| 68 | pub fn wait(cond: *WindowsCondition, mutex: *Mutex) void { | 147 | fn wait(self: *Impl, mutex: *Mutex, timeout: ?u64) error{Timeout}!void { |
| 69 | const rc = windows.kernel32.SleepConditionVariableSRW( | 148 | var timeout_overflowed = false; |
| 70 | &cond.cond, | 149 | var timeout_ms: os.windows.DWORD = os.windows.INFINITE; |
| 71 | &mutex.impl.srwlock, | ||
| 72 | windows.INFINITE, | ||
| 73 | @as(windows.ULONG, 0), | ||
| 74 | ); | ||
| 75 | assert(rc != windows.FALSE); | ||
| 76 | } | ||
| 77 | 150 | ||
| 78 | pub fn timedWait(cond: *WindowsCondition, mutex: *Mutex, timeout_ns: u64) error{TimedOut}!void { | 151 | if (timeout) |timeout_ns| { |
| 79 | var timeout_checked = std.math.cast(windows.DWORD, timeout_ns / std.time.ns_per_ms) catch overflow: { | 152 | // Round the nanoseconds to the nearest millisecond, |
| 80 | break :overflow std.math.maxInt(windows.DWORD); | 153 | // then saturating cast it to windows DWORD for use in kernel32 call. |
| 81 | }; | 154 | const ms = (timeout_ns +| (std.time.ns_per_ms / 2)) / std.time.ns_per_ms; |
| 155 | timeout_ms = std.math.cast(os.windows.DWORD, ms) catch std.math.maxInt(os.windows.DWORD); | ||
| 82 | 156 | ||
| 83 | // Handle the case where timeout is INFINITE, otherwise SleepConditionVariableSRW's time-out never elapses | 157 | // Track if the timeout overflowed into INFINITE and make sure not to wait forever. |
| 84 | const timeout_overflowed = timeout_checked == windows.INFINITE; | 158 | if (timeout_ms == os.windows.INFINITE) { |
| 85 | timeout_checked -= @boolToInt(timeout_overflowed); | 159 | timeout_overflowed = true; |
| 160 | timeout_ms -= 1; | ||
| 161 | } | ||
| 162 | } | ||
| 86 | 163 | ||
| 87 | const rc = windows.kernel32.SleepConditionVariableSRW( | 164 | const rc = os.windows.kernel32.SleepConditionVariableSRW( |
| 88 | &cond.cond, | 165 | &self.condition, |
| 89 | &mutex.impl.srwlock, | 166 | &mutex.impl.srwlock, |
| 90 | timeout_checked, | 167 | timeout_ms, |
| 91 | @as(windows.ULONG, 0), | 168 | 0, // the srwlock was assumed to acquired in exclusive mode not shared |
| 92 | ); | 169 | ); |
| 93 | if (rc == windows.FALSE and windows.kernel32.GetLastError() == windows.Win32Error.TIMEOUT) return error.TimedOut; | ||
| 94 | assert(rc != windows.FALSE); | ||
| 95 | } | ||
| 96 | 170 | ||
| 97 | pub fn signal(cond: *WindowsCondition) void { | 171 | // Return error.Timeout if we know the timeout elapsed correctly. |
| 98 | windows.kernel32.WakeConditionVariable(&cond.cond); | 172 | if (rc == os.windows.FALSE) { |
| 173 | assert(os.windows.kernel32.GetLastError() == .TIMEOUT); | ||
| 174 | if (!timeout_overflowed) return error.Timeout; | ||
| 175 | } | ||
| 99 | } | 176 | } |
| 100 | 177 | ||
| 101 | pub fn broadcast(cond: *WindowsCondition) void { | 178 | fn wake(self: *Impl, comptime notify: Notify) void { |
| 102 | windows.kernel32.WakeAllConditionVariable(&cond.cond); | 179 | switch (notify) { |
| 180 | .one => os.windows.kernel32.WakeConditionVariable(&self.condition), | ||
| 181 | .all => os.windows.kernel32.WakeAllConditionVariable(&self.condition), | ||
| 182 | } | ||
| 103 | } | 183 | } |
| 104 | }; | 184 | }; |
| 105 | 185 | ||
| 106 | pub const PthreadCondition = struct { | 186 | const FutexImpl = struct { |
| 107 | cond: std.c.pthread_cond_t = .{}, | 187 | state: Atomic(u32) = Atomic(u32).init(0), |
| 188 | epoch: Atomic(u32) = Atomic(u32).init(0), | ||
| 108 | 189 | ||
| 109 | pub fn wait(cond: *PthreadCondition, mutex: *Mutex) void { | 190 | const one_waiter = 1; |
| 110 | const rc = std.c.pthread_cond_wait(&cond.cond, &mutex.impl.pthread_mutex); | 191 | const waiter_mask = 0xffff; |
| 111 | assert(rc == .SUCCESS); | ||
| 112 | } | ||
| 113 | 192 | ||
| 114 | pub fn timedWait(cond: *PthreadCondition, mutex: *Mutex, timeout_ns: u64) error{TimedOut}!void { | 193 | const one_signal = 1 << 16; |
| 115 | var ts: std.os.timespec = undefined; | 194 | const signal_mask = 0xffff << 16; |
| 116 | std.os.clock_gettime(std.os.CLOCK.REALTIME, &ts) catch unreachable; | ||
| 117 | ts.tv_sec += @intCast(@TypeOf(ts.tv_sec), timeout_ns / std.time.ns_per_s); | ||
| 118 | ts.tv_nsec += @intCast(@TypeOf(ts.tv_nsec), timeout_ns % std.time.ns_per_s); | ||
| 119 | if (ts.tv_nsec >= std.time.ns_per_s) { | ||
| 120 | ts.tv_sec += 1; | ||
| 121 | ts.tv_nsec -= std.time.ns_per_s; | ||
| 122 | } | ||
| 123 | 195 | ||
| 124 | const rc = std.c.pthread_cond_timedwait(&cond.cond, &mutex.impl.pthread_mutex, &ts); | 196 | fn wait(self: *Impl, mutex: *Mutex, timeout: ?u64) error{Timeout}!void { |
| 125 | return switch (rc) { | 197 | // Register that we're waiting on the state by incrementing the wait count. |
| 126 | .SUCCESS => {}, | 198 | // This assumes that there can be at most ((1<<16)-1) or 65,355 threads concurrently waiting on the same Condvar. |
| 127 | .TIMEDOUT => error.TimedOut, | 199 | // If this is hit in practice, then this condvar not working is the least of your concerns. |
| 128 | else => unreachable, | 200 | var state = self.state.fetchAdd(one_waiter, .Monotonic); |
| 129 | }; | 201 | assert(state & waiter_mask != waiter_mask); |
| 130 | } | 202 | state += one_waiter; |
| 131 | 203 | ||
| 132 | pub fn signal(cond: *PthreadCondition) void { | 204 | // Temporarily release the mutex in order to block on the condition variable. |
| 133 | const rc = std.c.pthread_cond_signal(&cond.cond); | 205 | mutex.unlock(); |
| 134 | assert(rc == .SUCCESS); | 206 | defer mutex.lock(); |
| 135 | } | ||
| 136 | 207 | ||
| 137 | pub fn broadcast(cond: *PthreadCondition) void { | 208 | var futex_deadline = Futex.Deadline.init(timeout); |
| 138 | const rc = std.c.pthread_cond_broadcast(&cond.cond); | 209 | while (true) { |
| 139 | assert(rc == .SUCCESS); | 210 | // Try to wake up by consuming a signal and decremented the waiter we added previously. |
| 140 | } | 211 | // Acquire barrier ensures code before the wake() which added the signal happens before we decrement it and return. |
| 141 | }; | 212 | while (state & signal_mask != 0) { |
| 213 | const new_state = state - one_waiter - one_signal; | ||
| 214 | state = self.state.tryCompareAndSwap(state, new_state, .Acquire, .Monotonic) orelse return; | ||
| 215 | } | ||
| 142 | 216 | ||
| 143 | pub const AtomicCondition = struct { | 217 | // Observe the epoch, then check the state again to see if we should wake up. |
| 144 | pending: bool = false, | 218 | // The epoch must be observed before we check the state or we could potentially miss a wake() and deadlock: |
| 145 | queue_mutex: Mutex = .{}, | 219 | // |
| 146 | queue_list: QueueList = .{}, | 220 | // - T1: s = LOAD(&state) |
| 147 | 221 | // - T2: UPDATE(&s, signal) | |
| 148 | pub const QueueList = std.SinglyLinkedList(QueueItem); | 222 | // - T2: UPDATE(&epoch, 1) + FUTEX_WAKE(&epoch) |
| 149 | 223 | // - T1: e = LOAD(&epoch) (was reordered after the state load) | |
| 150 | pub const QueueItem = struct { | 224 | // - T1: s & signals == 0 -> FUTEX_WAIT(&epoch, e) (missed the state update + the epoch change) |
| 151 | futex: i32 = 0, | 225 | // |
| 152 | dequeued: bool = false, | 226 | // Acquire barrier to ensure the epoch load happens before the state load. |
| 153 | 227 | const epoch = self.epoch.load(.Acquire); | |
| 154 | fn wait(cond: *@This()) void { | 228 | state = self.state.load(.Monotonic); |
| 155 | while (@atomicLoad(i32, &cond.futex, .Acquire) == 0) { | 229 | if (state & signal_mask != 0) { |
| 156 | switch (builtin.os.tag) { | 230 | continue; |
| 157 | .linux => { | ||
| 158 | switch (linux.getErrno(linux.futex_wait( | ||
| 159 | &cond.futex, | ||
| 160 | linux.FUTEX.PRIVATE_FLAG | linux.FUTEX.WAIT, | ||
| 161 | 0, | ||
| 162 | null, | ||
| 163 | ))) { | ||
| 164 | .SUCCESS => {}, | ||
| 165 | .INTR => {}, | ||
| 166 | .AGAIN => {}, | ||
| 167 | else => unreachable, | ||
| 168 | } | ||
| 169 | }, | ||
| 170 | else => std.atomic.spinLoopHint(), | ||
| 171 | } | ||
| 172 | } | 231 | } |
| 173 | } | ||
| 174 | 232 | ||
| 175 | pub fn timedWait(cond: *@This(), timeout_ns: u64) error{TimedOut}!void { | 233 | futex_deadline.wait(&self.epoch, epoch) catch |err| switch (err) { |
| 176 | const start_time = std.time.nanoTimestamp(); | 234 | // On timeout, we must decrement the waiter we added above. |
| 177 | while (@atomicLoad(i32, &cond.futex, .Acquire) == 0) { | 235 | error.Timeout => { |
| 178 | switch (builtin.os.tag) { | 236 | while (true) { |
| 179 | .linux => { | 237 | // If there's a signal when we're timing out, consume it and report being woken up instead. |
| 180 | var ts: std.os.timespec = undefined; | 238 | // Acquire barrier ensures code before the wake() which added the signal happens before we decrement it and return. |
| 181 | ts.tv_sec = @intCast(@TypeOf(ts.tv_sec), timeout_ns / std.time.ns_per_s); | 239 | while (state & signal_mask != 0) { |
| 182 | ts.tv_nsec = @intCast(@TypeOf(ts.tv_nsec), timeout_ns % std.time.ns_per_s); | 240 | const new_state = state - one_waiter - one_signal; |
| 183 | switch (linux.getErrno(linux.futex_wait( | 241 | state = self.state.tryCompareAndSwap(state, new_state, .Acquire, .Monotonic) orelse return; |
| 184 | &cond.futex, | ||
| 185 | linux.FUTEX.PRIVATE_FLAG | linux.FUTEX.WAIT, | ||
| 186 | 0, | ||
| 187 | &ts, | ||
| 188 | ))) { | ||
| 189 | .SUCCESS => {}, | ||
| 190 | .INTR => {}, | ||
| 191 | .AGAIN => {}, | ||
| 192 | .TIMEDOUT => return error.TimedOut, | ||
| 193 | .INVAL => {}, // possibly timeout overflow | ||
| 194 | .FAULT => unreachable, | ||
| 195 | else => unreachable, | ||
| 196 | } | ||
| 197 | }, | ||
| 198 | else => { | ||
| 199 | if (std.time.nanoTimestamp() - start_time >= timeout_ns) { | ||
| 200 | return error.TimedOut; | ||
| 201 | } | 242 | } |
| 202 | std.atomic.spinLoopHint(); | ||
| 203 | }, | ||
| 204 | } | ||
| 205 | } | ||
| 206 | } | ||
| 207 | 243 | ||
| 208 | fn notify(cond: *@This()) void { | 244 | // Remove the waiter we added and officially return timed out. |
| 209 | @atomicStore(i32, &cond.futex, 1, .Release); | 245 | const new_state = state - one_waiter; |
| 210 | 246 | state = self.state.tryCompareAndSwap(state, new_state, .Monotonic, .Monotonic) orelse return err; | |
| 211 | switch (builtin.os.tag) { | ||
| 212 | .linux => { | ||
| 213 | switch (linux.getErrno(linux.futex_wake( | ||
| 214 | &cond.futex, | ||
| 215 | linux.FUTEX.PRIVATE_FLAG | linux.FUTEX.WAKE, | ||
| 216 | 1, | ||
| 217 | ))) { | ||
| 218 | .SUCCESS => {}, | ||
| 219 | .FAULT => {}, | ||
| 220 | else => unreachable, | ||
| 221 | } | 247 | } |
| 222 | }, | 248 | }, |
| 223 | else => {}, | 249 | }; |
| 250 | } | ||
| 251 | } | ||
| 252 | |||
| 253 | fn wake(self: *Impl, comptime notify: Notify) void { | ||
| 254 | var state = self.state.load(.Monotonic); | ||
| 255 | while (true) { | ||
| 256 | const waiters = (state & waiter_mask) / one_waiter; | ||
| 257 | const signals = (state & signal_mask) / one_signal; | ||
| 258 | |||
| 259 | // Reserves which waiters to wake up by incrementing the signals count. | ||
| 260 | // Therefor, the signals count is always less than or equal to the waiters count. | ||
| 261 | // We don't need to Futex.wake if there's nothing to wake up or if other wake() threads have reserved to wake up the current waiters. | ||
| 262 | const wakeable = waiters - signals; | ||
| 263 | if (wakeable == 0) { | ||
| 264 | return; | ||
| 224 | } | 265 | } |
| 266 | |||
| 267 | const to_wake = switch (notify) { | ||
| 268 | .one => 1, | ||
| 269 | .all => wakeable, | ||
| 270 | }; | ||
| 271 | |||
| 272 | // Reserve the amount of waiters to wake by incrementing the signals count. | ||
| 273 | // Release barrier ensures code before the wake() happens before the signal it posted and consumed by the wait() threads. | ||
| 274 | const new_state = state + (one_signal * to_wake); | ||
| 275 | state = self.state.tryCompareAndSwap(state, new_state, .Release, .Monotonic) orelse { | ||
| 276 | // Wake up the waiting threads we reserved above by changing the epoch value. | ||
| 277 | // NOTE: a waiting thread could miss a wake up if *exactly* ((1<<32)-1) wake()s happen between it observing the epoch and sleeping on it. | ||
| 278 | // This is very unlikely due to how many precise amount of Futex.wake() calls that would be between the waiting thread's potential preemption. | ||
| 279 | // | ||
| 280 | // Release barrier ensures the signal being added to the state happens before the epoch is changed. | ||
| 281 | // If not, the waiting thread could potentially deadlock from missing both the state and epoch change: | ||
| 282 | // | ||
| 283 | // - T2: UPDATE(&epoch, 1) (reordered before the state change) | ||
| 284 | // - T1: e = LOAD(&epoch) | ||
| 285 | // - T1: s = LOAD(&state) | ||
| 286 | // - T2: UPDATE(&state, signal) + FUTEX_WAKE(&epoch) | ||
| 287 | // - T1: s & signals == 0 -> FUTEX_WAIT(&epoch, e) (missed both epoch change and state change) | ||
| 288 | _ = self.epoch.fetchAdd(1, .Release); | ||
| 289 | Futex.wake(&self.epoch, to_wake); | ||
| 290 | return; | ||
| 291 | }; | ||
| 225 | } | 292 | } |
| 226 | }; | 293 | } |
| 294 | }; | ||
| 227 | 295 | ||
| 228 | pub fn wait(cond: *AtomicCondition, mutex: *Mutex) void { | 296 | test "Condition - smoke test" { |
| 229 | var waiter = QueueList.Node{ .data = .{} }; | 297 | var mutex = Mutex{}; |
| 298 | var cond = Condition{}; | ||
| 230 | 299 | ||
| 231 | { | 300 | // Try to wake outside the mutex |
| 232 | cond.queue_mutex.lock(); | 301 | defer cond.signal(); |
| 233 | defer cond.queue_mutex.unlock(); | 302 | defer cond.broadcast(); |
| 234 | 303 | ||
| 235 | cond.queue_list.prepend(&waiter); | 304 | mutex.lock(); |
| 236 | @atomicStore(bool, &cond.pending, true, .SeqCst); | 305 | defer mutex.unlock(); |
| 237 | } | ||
| 238 | 306 | ||
| 239 | mutex.unlock(); | 307 | // Try to wait with a timeout (should not deadlock) |
| 240 | waiter.data.wait(); | 308 | try testing.expectError(error.Timeout, cond.timedWait(&mutex, 0)); |
| 241 | mutex.lock(); | 309 | try testing.expectError(error.Timeout, cond.timedWait(&mutex, std.time.ns_per_ms)); |
| 242 | } | ||
| 243 | 310 | ||
| 244 | pub fn timedWait(cond: *AtomicCondition, mutex: *Mutex, timeout_ns: u64) error{TimedOut}!void { | 311 | // Try to wake inside the mutex. |
| 245 | var waiter = QueueList.Node{ .data = .{} }; | 312 | cond.signal(); |
| 313 | cond.broadcast(); | ||
| 314 | } | ||
| 246 | 315 | ||
| 247 | { | 316 | // Inspired from: https://github.com/Amanieu/parking_lot/pull/129 |
| 248 | cond.queue_mutex.lock(); | 317 | test "Condition - wait and signal" { |
| 249 | defer cond.queue_mutex.unlock(); | 318 | // This test requires spawning threads |
| 319 | if (builtin.single_threaded) { | ||
| 320 | return error.SkipZigTest; | ||
| 321 | } | ||
| 250 | 322 | ||
| 251 | cond.queue_list.prepend(&waiter); | 323 | const num_threads = 4; |
| 252 | @atomicStore(bool, &cond.pending, true, .SeqCst); | ||
| 253 | } | ||
| 254 | 324 | ||
| 255 | var timed_out = false; | 325 | const MultiWait = struct { |
| 256 | mutex.unlock(); | 326 | mutex: Mutex = .{}, |
| 257 | defer mutex.lock(); | 327 | cond: Condition = .{}, |
| 258 | waiter.data.timedWait(timeout_ns) catch |err| switch (err) { | 328 | threads: [num_threads]std.Thread = undefined, |
| 259 | error.TimedOut => { | ||
| 260 | defer if (!timed_out) { | ||
| 261 | waiter.data.wait(); | ||
| 262 | }; | ||
| 263 | cond.queue_mutex.lock(); | ||
| 264 | defer cond.queue_mutex.unlock(); | ||
| 265 | |||
| 266 | if (!waiter.data.dequeued) { | ||
| 267 | timed_out = true; | ||
| 268 | cond.queue_list.remove(&waiter); | ||
| 269 | } | ||
| 270 | }, | ||
| 271 | else => unreachable, | ||
| 272 | }; | ||
| 273 | 329 | ||
| 274 | if (timed_out) { | 330 | fn run(self: *@This()) void { |
| 275 | return error.TimedOut; | 331 | self.mutex.lock(); |
| 332 | defer self.mutex.unlock(); | ||
| 333 | |||
| 334 | self.cond.wait(&self.mutex); | ||
| 335 | self.cond.timedWait(&self.mutex, std.time.ns_per_ms) catch {}; | ||
| 336 | self.cond.signal(); | ||
| 276 | } | 337 | } |
| 338 | }; | ||
| 339 | |||
| 340 | var multi_wait = MultiWait{}; | ||
| 341 | for (multi_wait.threads) |*t| { | ||
| 342 | t.* = try std.Thread.spawn(.{}, MultiWait.run, .{&multi_wait}); | ||
| 277 | } | 343 | } |
| 278 | 344 | ||
| 279 | pub fn signal(cond: *AtomicCondition) void { | 345 | std.time.sleep(100 * std.time.ns_per_ms); |
| 280 | if (@atomicLoad(bool, &cond.pending, .SeqCst) == false) | ||
| 281 | return; | ||
| 282 | 346 | ||
| 283 | const maybe_waiter = blk: { | 347 | multi_wait.cond.signal(); |
| 284 | cond.queue_mutex.lock(); | 348 | for (multi_wait.threads) |t| { |
| 285 | defer cond.queue_mutex.unlock(); | 349 | t.join(); |
| 350 | } | ||
| 351 | } | ||
| 286 | 352 | ||
| 287 | const maybe_waiter = cond.queue_list.popFirst(); | 353 | test "Condition - signal" { |
| 288 | if (maybe_waiter) |waiter| { | 354 | // This test requires spawning threads |
| 289 | waiter.data.dequeued = true; | 355 | if (builtin.single_threaded) { |
| 356 | return error.SkipZigTest; | ||
| 357 | } | ||
| 358 | |||
| 359 | const num_threads = 4; | ||
| 360 | |||
| 361 | const SignalTest = struct { | ||
| 362 | mutex: Mutex = .{}, | ||
| 363 | cond: Condition = .{}, | ||
| 364 | notified: bool = false, | ||
| 365 | threads: [num_threads]std.Thread = undefined, | ||
| 366 | |||
| 367 | fn run(self: *@This()) void { | ||
| 368 | self.mutex.lock(); | ||
| 369 | defer self.mutex.unlock(); | ||
| 370 | |||
| 371 | // Use timedWait() a few times before using wait() | ||
| 372 | // to test multiple threads timing out frequently. | ||
| 373 | var i: usize = 0; | ||
| 374 | while (!self.notified) : (i +%= 1) { | ||
| 375 | if (i < 5) { | ||
| 376 | self.cond.timedWait(&self.mutex, 1) catch {}; | ||
| 377 | } else { | ||
| 378 | self.cond.wait(&self.mutex); | ||
| 379 | } | ||
| 290 | } | 380 | } |
| 291 | @atomicStore(bool, &cond.pending, cond.queue_list.first != null, .SeqCst); | ||
| 292 | break :blk maybe_waiter; | ||
| 293 | }; | ||
| 294 | 381 | ||
| 295 | if (maybe_waiter) |waiter| { | 382 | // Once we received the signal, notify another thread (inside the lock). |
| 296 | waiter.data.notify(); | 383 | assert(self.notified); |
| 384 | self.cond.signal(); | ||
| 297 | } | 385 | } |
| 298 | } | 386 | }; |
| 299 | |||
| 300 | pub fn broadcast(cond: *AtomicCondition) void { | ||
| 301 | if (@atomicLoad(bool, &cond.pending, .SeqCst) == false) | ||
| 302 | return; | ||
| 303 | 387 | ||
| 304 | @atomicStore(bool, &cond.pending, false, .SeqCst); | 388 | var signal_test = SignalTest{}; |
| 389 | for (signal_test.threads) |*t| { | ||
| 390 | t.* = try std.Thread.spawn(.{}, SignalTest.run, .{&signal_test}); | ||
| 391 | } | ||
| 305 | 392 | ||
| 306 | var waiters = blk: { | 393 | { |
| 307 | cond.queue_mutex.lock(); | 394 | // Wait for a bit in hopes that the spawned threads start queuing up on the condvar |
| 308 | defer cond.queue_mutex.unlock(); | 395 | std.time.sleep(10 * std.time.ns_per_ms); |
| 309 | 396 | ||
| 310 | const waiters = cond.queue_list; | 397 | // Wake up one of them (outside the lock) after setting notified=true. |
| 398 | defer signal_test.cond.signal(); | ||
| 311 | 399 | ||
| 312 | var it = waiters.first; | 400 | signal_test.mutex.lock(); |
| 313 | while (it) |node| : (it = node.next) { | 401 | defer signal_test.mutex.unlock(); |
| 314 | node.data.dequeued = true; | ||
| 315 | } | ||
| 316 | 402 | ||
| 317 | cond.queue_list = .{}; | 403 | try testing.expect(!signal_test.notified); |
| 318 | break :blk waiters; | 404 | signal_test.notified = true; |
| 319 | }; | 405 | } |
| 320 | 406 | ||
| 321 | while (waiters.popFirst()) |waiter| { | 407 | for (signal_test.threads) |t| { |
| 322 | waiter.data.notify(); | 408 | t.join(); |
| 323 | } | ||
| 324 | } | 409 | } |
| 325 | }; | 410 | } |
| 326 | 411 | ||
| 327 | test "Thread.Condition" { | 412 | test "Condition - multi signal" { |
| 413 | // This test requires spawning threads | ||
| 328 | if (builtin.single_threaded) { | 414 | if (builtin.single_threaded) { |
| 329 | return error.SkipZigTest; | 415 | return error.SkipZigTest; |
| 330 | } | 416 | } |
| 331 | 417 | ||
| 332 | const TestContext = struct { | 418 | const num_threads = 4; |
| 333 | cond: *Condition, | 419 | const num_iterations = 4; |
| 334 | cond_main: *Condition, | 420 | |
| 335 | mutex: *Mutex, | 421 | const Paddle = struct { |
| 336 | n: *i32, | 422 | mutex: Mutex = .{}, |
| 337 | fn worker(ctx: *@This()) void { | 423 | cond: Condition = .{}, |
| 338 | ctx.mutex.lock(); | 424 | value: u32 = 0, |
| 339 | ctx.n.* += 1; | 425 | |
| 340 | ctx.cond_main.signal(); | 426 | fn hit(self: *@This()) void { |
| 341 | ctx.cond.wait(ctx.mutex); | 427 | defer self.cond.signal(); |
| 342 | ctx.n.* -= 1; | 428 | |
| 343 | ctx.cond_main.signal(); | 429 | self.mutex.lock(); |
| 344 | ctx.mutex.unlock(); | 430 | defer self.mutex.unlock(); |
| 431 | |||
| 432 | self.value += 1; | ||
| 345 | } | 433 | } |
| 346 | }; | ||
| 347 | const num_threads = 3; | ||
| 348 | var threads: [num_threads]std.Thread = undefined; | ||
| 349 | var cond = Condition{}; | ||
| 350 | var cond_main = Condition{}; | ||
| 351 | var mut = Mutex{}; | ||
| 352 | var n: i32 = 0; | ||
| 353 | var ctx = TestContext{ .cond = &cond, .cond_main = &cond_main, .mutex = &mut, .n = &n }; | ||
| 354 | 434 | ||
| 355 | mut.lock(); | 435 | fn run(self: *@This(), hit_to: *@This()) !void { |
| 356 | for (threads) |*t| t.* = try std.Thread.spawn(.{}, TestContext.worker, .{&ctx}); | 436 | self.mutex.lock(); |
| 357 | cond_main.wait(&mut); | 437 | defer self.mutex.unlock(); |
| 358 | while (n < num_threads) cond_main.wait(&mut); | ||
| 359 | 438 | ||
| 360 | cond.signal(); | 439 | var current: u32 = 0; |
| 361 | cond_main.wait(&mut); | 440 | while (current < num_iterations) : (current += 1) { |
| 362 | try testing.expect(n == (num_threads - 1)); | 441 | // Wait for the value to change from hit() |
| 442 | while (self.value == current) { | ||
| 443 | self.cond.wait(&self.mutex); | ||
| 444 | } | ||
| 363 | 445 | ||
| 364 | cond.broadcast(); | 446 | // hit the next paddle |
| 365 | while (n > 0) cond_main.wait(&mut); | 447 | try testing.expectEqual(self.value, current + 1); |
| 366 | try testing.expect(n == 0); | 448 | hit_to.hit(); |
| 449 | } | ||
| 450 | } | ||
| 451 | }; | ||
| 452 | |||
| 453 | var paddles = [_]Paddle{.{}} ** num_threads; | ||
| 454 | var threads = [_]std.Thread{undefined} ** num_threads; | ||
| 455 | |||
| 456 | // Create a circle of paddles which hit each other | ||
| 457 | for (threads) |*t, i| { | ||
| 458 | const paddle = &paddles[i]; | ||
| 459 | const hit_to = &paddles[(i + 1) % paddles.len]; | ||
| 460 | t.* = try std.Thread.spawn(.{}, Paddle.run, .{ paddle, hit_to }); | ||
| 461 | } | ||
| 367 | 462 | ||
| 463 | // Hit the first paddle and wait for them all to complete by hitting each other for num_iterations. | ||
| 464 | paddles[0].hit(); | ||
| 368 | for (threads) |t| t.join(); | 465 | for (threads) |t| t.join(); |
| 466 | |||
| 467 | // The first paddle will be hit one last time by the last paddle. | ||
| 468 | for (paddles) |p, i| { | ||
| 469 | const expected = @as(u32, num_iterations) + @boolToInt(i == 0); | ||
| 470 | try testing.expectEqual(p.value, expected); | ||
| 471 | } | ||
| 369 | } | 472 | } |
| 370 | 473 | ||
| 371 | test "Thread.Condition.timedWait" { | 474 | test "Condition - broadcasting" { |
| 475 | // This test requires spawning threads | ||
| 372 | if (builtin.single_threaded) { | 476 | if (builtin.single_threaded) { |
| 373 | return error.SkipZigTest; | 477 | return error.SkipZigTest; |
| 374 | } | 478 | } |
| 375 | 479 | ||
| 376 | var cond = Condition{}; | 480 | const num_threads = 10; |
| 377 | var mut = Mutex{}; | ||
| 378 | 481 | ||
| 379 | // Expect a timeout, as the condition variable is never signaled | 482 | const BroadcastTest = struct { |
| 380 | { | 483 | mutex: Mutex = .{}, |
| 381 | mut.lock(); | 484 | cond: Condition = .{}, |
| 382 | defer mut.unlock(); | 485 | completed: Condition = .{}, |
| 383 | try testing.expectError(error.TimedOut, cond.timedWait(&mut, 10 * std.time.ns_per_ms)); | 486 | count: usize = 0, |
| 487 | threads: [num_threads]std.Thread = undefined, | ||
| 488 | |||
| 489 | fn run(self: *@This()) void { | ||
| 490 | self.mutex.lock(); | ||
| 491 | defer self.mutex.unlock(); | ||
| 492 | |||
| 493 | // The last broadcast thread to start tells the main test thread it's completed. | ||
| 494 | self.count += 1; | ||
| 495 | if (self.count == num_threads) { | ||
| 496 | self.completed.signal(); | ||
| 497 | } | ||
| 498 | |||
| 499 | // Waits for the count to reach zero after the main test thread observes it at num_threads. | ||
| 500 | // Tries to use timedWait() a bit before falling back to wait() to test multiple threads timing out. | ||
| 501 | var i: usize = 0; | ||
| 502 | while (self.count != 0) : (i +%= 1) { | ||
| 503 | if (i < 10) { | ||
| 504 | self.cond.timedWait(&self.mutex, 1) catch {}; | ||
| 505 | } else { | ||
| 506 | self.cond.wait(&self.mutex); | ||
| 507 | } | ||
| 508 | } | ||
| 509 | } | ||
| 510 | }; | ||
| 511 | |||
| 512 | var broadcast_test = BroadcastTest{}; | ||
| 513 | for (broadcast_test.threads) |*t| { | ||
| 514 | t.* = try std.Thread.spawn(.{}, BroadcastTest.run, .{&broadcast_test}); | ||
| 384 | } | 515 | } |
| 385 | 516 | ||
| 386 | // Expect a signal before timeout | ||
| 387 | { | 517 | { |
| 388 | const TestContext = struct { | 518 | broadcast_test.mutex.lock(); |
| 389 | cond: *Condition, | 519 | defer broadcast_test.mutex.unlock(); |
| 390 | mutex: *Mutex, | 520 | |
| 391 | n: *u32, | 521 | // Wait for all the broadcast threads to spawn. |
| 392 | fn worker(ctx: *@This()) void { | 522 | // timedWait() to detect any potential deadlocks. |
| 393 | ctx.mutex.lock(); | 523 | while (broadcast_test.count != num_threads) { |
| 394 | defer ctx.mutex.unlock(); | 524 | try broadcast_test.completed.timedWait( |
| 395 | ctx.n.* = 1; | 525 | &broadcast_test.mutex, |
| 396 | ctx.cond.signal(); | 526 | 1 * std.time.ns_per_s, |
| 397 | } | 527 | ); |
| 398 | }; | 528 | } |
| 399 | 529 | ||
| 400 | var n: u32 = 0; | 530 | // Reset the counter and wake all the threads to exit. |
| 531 | broadcast_test.count = 0; | ||
| 532 | broadcast_test.cond.broadcast(); | ||
| 533 | } | ||
| 401 | 534 | ||
| 402 | var ctx = TestContext{ .cond = &cond, .mutex = &mut, .n = &n }; | 535 | for (broadcast_test.threads) |t| { |
| 403 | mut.lock(); | 536 | t.join(); |
| 404 | var thread = try std.Thread.spawn(.{}, TestContext.worker, .{&ctx}); | ||
| 405 | // Looped check to handle spurious wakeups | ||
| 406 | while (n != 1) try cond.timedWait(&mut, 500 * std.time.ns_per_ms); | ||
| 407 | mut.unlock(); | ||
| 408 | try testing.expect(n == 1); | ||
| 409 | thread.join(); | ||
| 410 | } | 537 | } |
| 411 | } | 538 | } |
lib/std/Thread/Futex.zig+62-7| ... | @@ -10,14 +10,12 @@ const Futex = @This(); | ... | @@ -10,14 +10,12 @@ const Futex = @This(); |
| 10 | const os = std.os; | 10 | const os = std.os; |
| 11 | const assert = std.debug.assert; | 11 | const assert = std.debug.assert; |
| 12 | const testing = std.testing; | 12 | const testing = std.testing; |
| 13 | |||
| 14 | const Atomic = std.atomic.Atomic; | 13 | const Atomic = std.atomic.Atomic; |
| 15 | const spinLoopHint = std.atomic.spinLoopHint; | ||
| 16 | 14 | ||
| 17 | /// Checks if `ptr` still contains the value `expect` and, if so, blocks the caller until either: | 15 | /// Checks if `ptr` still contains the value `expect` and, if so, blocks the caller until either: |
| 18 | /// - The value at `ptr` is no longer equal to `expect`. | 16 | /// - The value at `ptr` is no longer equal to `expect`. |
| 19 | /// - The caller is unblocked by a matching `wake()`. | 17 | /// - The caller is unblocked by a matching `wake()`. |
| 20 | /// - The caller is unblocked spuriously by an arbitrary internal signal. | 18 | /// - The caller is unblocked spuriously ("at random"). |
| 21 | /// | 19 | /// |
| 22 | /// The checking of `ptr` and `expect`, along with blocking the caller, is done atomically | 20 | /// The checking of `ptr` and `expect`, along with blocking the caller, is done atomically |
| 23 | /// and totally ordered (sequentially consistent) with respect to other wait()/wake() calls on the same `ptr`. | 21 | /// and totally ordered (sequentially consistent) with respect to other wait()/wake() calls on the same `ptr`. |
| ... | @@ -32,7 +30,7 @@ pub fn wait(ptr: *const Atomic(u32), expect: u32) void { | ... | @@ -32,7 +30,7 @@ pub fn wait(ptr: *const Atomic(u32), expect: u32) void { |
| 32 | /// Checks if `ptr` still contains the value `expect` and, if so, blocks the caller until either: | 30 | /// Checks if `ptr` still contains the value `expect` and, if so, blocks the caller until either: |
| 33 | /// - The value at `ptr` is no longer equal to `expect`. | 31 | /// - The value at `ptr` is no longer equal to `expect`. |
| 34 | /// - The caller is unblocked by a matching `wake()`. | 32 | /// - The caller is unblocked by a matching `wake()`. |
| 35 | /// - The caller is unblocked spuriously by an arbitrary internal signal. | 33 | /// - The caller is unblocked spuriously ("at random"). |
| 36 | /// - The caller blocks for longer than the given timeout. In which case, `error.Timeout` is returned. | 34 | /// - The caller blocks for longer than the given timeout. In which case, `error.Timeout` is returned. |
| 37 | /// | 35 | /// |
| 38 | /// The checking of `ptr` and `expect`, along with blocking the caller, is done atomically | 36 | /// The checking of `ptr` and `expect`, along with blocking the caller, is done atomically |
| ... | @@ -62,7 +60,7 @@ pub fn wake(ptr: *const Atomic(u32), max_waiters: u32) void { | ... | @@ -62,7 +60,7 @@ pub fn wake(ptr: *const Atomic(u32), max_waiters: u32) void { |
| 62 | } | 60 | } |
| 63 | 61 | ||
| 64 | const Impl = if (builtin.single_threaded) | 62 | const Impl = if (builtin.single_threaded) |
| 65 | SerialImpl | 63 | SingleThreadedImpl |
| 66 | else if (builtin.os.tag == .windows) | 64 | else if (builtin.os.tag == .windows) |
| 67 | WindowsImpl | 65 | WindowsImpl |
| 68 | else if (builtin.os.tag.isDarwin()) | 66 | else if (builtin.os.tag.isDarwin()) |
| ... | @@ -97,7 +95,7 @@ const UnsupportedImpl = struct { | ... | @@ -97,7 +95,7 @@ const UnsupportedImpl = struct { |
| 97 | } | 95 | } |
| 98 | }; | 96 | }; |
| 99 | 97 | ||
| 100 | const SerialImpl = struct { | 98 | const SingleThreadedImpl = struct { |
| 101 | fn wait(ptr: *const Atomic(u32), expect: u32, timeout: ?u64) error{Timeout}!void { | 99 | fn wait(ptr: *const Atomic(u32), expect: u32, timeout: ?u64) error{Timeout}!void { |
| 102 | if (ptr.loadUnchecked() != expect) { | 100 | if (ptr.loadUnchecked() != expect) { |
| 103 | return; | 101 | return; |
| ... | @@ -804,7 +802,7 @@ const PosixImpl = struct { | ... | @@ -804,7 +802,7 @@ const PosixImpl = struct { |
| 804 | // | 802 | // |
| 805 | // What we really want here is a Release load, but that doesn't exist under the C11 memory model. | 803 | // What we really want here is a Release load, but that doesn't exist under the C11 memory model. |
| 806 | // We could instead do `bucket.pending.fetchAdd(0, Release) == 0` which achieves effectively the same thing, | 804 | // We could instead do `bucket.pending.fetchAdd(0, Release) == 0` which achieves effectively the same thing, |
| 807 | // but the RMW operation unconditionally stores which invalidates the cache-line for others causing unnecessary contention. | 805 | // but the RMW operation unconditionally marks the cache-line as modified for others causing unnecessary fetching/contention. |
| 808 | // | 806 | // |
| 809 | // Instead we opt to do a full-fence + load instead which avoids taking ownership of the cache-line. | 807 | // Instead we opt to do a full-fence + load instead which avoids taking ownership of the cache-line. |
| 810 | // fence(SeqCst) effectively converts the ptr update to SeqCst and the pending load to SeqCst: creating a Store-Load barrier. | 808 | // fence(SeqCst) effectively converts the ptr update to SeqCst and the pending load to SeqCst: creating a Store-Load barrier. |
| ... | @@ -962,3 +960,60 @@ test "Futex - broadcasting" { | ... | @@ -962,3 +960,60 @@ test "Futex - broadcasting" { |
| 962 | for (broadcast.threads) |*t| t.* = try std.Thread.spawn(.{}, Broadcast.run, .{&broadcast}); | 960 | for (broadcast.threads) |*t| t.* = try std.Thread.spawn(.{}, Broadcast.run, .{&broadcast}); |
| 963 | for (broadcast.threads) |t| t.join(); | 961 | for (broadcast.threads) |t| t.join(); |
| 964 | } | 962 | } |
| 963 | |||
| 964 | /// Deadline is used to wait efficiently for a pointer's value to change using Futex and a fixed timeout. | ||
| 965 | /// | ||
| 966 | /// Futex's timedWait() api uses a relative duration which suffers from over-waiting | ||
| 967 | /// when used in a loop which is often required due to the possibility of spurious wakeups. | ||
| 968 | /// | ||
| 969 | /// Deadline instead converts the relative timeout to an absolute one so that multiple calls | ||
| 970 | /// to Futex timedWait() can block for and report more accurate error.Timeouts. | ||
| 971 | pub const Deadline = struct { | ||
| 972 | timeout: ?u64, | ||
| 973 | started: std.time.Timer, | ||
| 974 | |||
| 975 | /// Create the deadline to expire after the given amount of time in nanoseconds passes. | ||
| 976 | /// Pass in `null` to have the deadline call `Futex.wait()` and never expire. | ||
| 977 | pub fn init(expires_in_ns: ?u64) Deadline { | ||
| 978 | var deadline: Deadline = undefined; | ||
| 979 | deadline.timeout = expires_in_ns; | ||
| 980 | |||
| 981 | // std.time.Timer is required to be supported for somewhat accurate reportings of error.Timeout. | ||
| 982 | if (deadline.timeout != null) { | ||
| 983 | deadline.started = std.time.Timer.start() catch unreachable; | ||
| 984 | } | ||
| 985 | |||
| 986 | return deadline; | ||
| 987 | } | ||
| 988 | |||
| 989 | /// Wait until either: | ||
| 990 | /// - the `ptr`'s value changes from `expect`. | ||
| 991 | /// - `Futex.wake()` is called on the `ptr`. | ||
| 992 | /// - A spurious wake occurs. | ||
| 993 | /// - The deadline expires; In which case `error.Timeout` is returned. | ||
| 994 | pub fn wait(self: *Deadline, ptr: *const Atomic(u32), expect: u32) error{Timeout}!void { | ||
| 995 | @setCold(true); | ||
| 996 | |||
| 997 | // Check if we actually have a timeout to wait until. | ||
| 998 | // If not just wait "forever". | ||
| 999 | const timeout_ns = self.timeout orelse { | ||
| 1000 | return Futex.wait(ptr, expect); | ||
| 1001 | }; | ||
| 1002 | |||
| 1003 | // Get how much time has passed since we started waiting | ||
| 1004 | // then subtract that from the init() timeout to get how much longer to wait. | ||
| 1005 | // Use overflow to detect when we've been waiting longer than the init() timeout. | ||
| 1006 | const elapsed_ns = self.started.read(); | ||
| 1007 | const until_timeout_ns = std.math.sub(u64, timeout_ns, elapsed_ns) catch 0; | ||
| 1008 | return Futex.timedWait(ptr, expect, until_timeout_ns); | ||
| 1009 | } | ||
| 1010 | }; | ||
| 1011 | |||
| 1012 | test "Futex - Deadline" { | ||
| 1013 | var deadline = Deadline.init(100 * std.time.ns_per_ms); | ||
| 1014 | var futex_word = Atomic(u32).init(0); | ||
| 1015 | |||
| 1016 | while (true) { | ||
| 1017 | deadline.wait(&futex_word, 0) catch break; | ||
| 1018 | } | ||
| 1019 | } |
lib/std/Thread/Mutex.zig+208-211| ... | @@ -1,288 +1,285 @@ | ... | @@ -1,288 +1,285 @@ |
| 1 | //! Lock may be held only once. If the same thread tries to acquire | 1 | //! Mutex is a synchronization primitive which enforces atomic access to a shared region of code known as the "critical section". |
| 2 | //! the same mutex twice, it deadlocks. This type supports static | 2 | //! It does this by blocking ensuring only one thread is in the critical section at any given point in time by blocking the others. |
| 3 | //! initialization and is at most `@sizeOf(usize)` in size. When an | 3 | //! Mutex can be statically initialized and is at most `@sizeOf(u64)` large. |
| 4 | //! application is built in single threaded release mode, all the | 4 | //! Use `lock()` or `tryLock()` to enter the critical section and `unlock()` to leave it. |
| 5 | //! functions are no-ops. In single threaded debug mode, there is | ||
| 6 | //! deadlock detection. | ||
| 7 | //! | 5 | //! |
| 8 | //! Example usage: | 6 | //! Example: |
| 7 | //! ``` | ||
| 9 | //! var m = Mutex{}; | 8 | //! var m = Mutex{}; |
| 10 | //! | 9 | //! |
| 11 | //! m.lock(); | 10 | //! { |
| 12 | //! defer m.release(); | 11 | //! m.lock(); |
| 13 | //! ... critical code | 12 | //! defer m.unlock(); |
| 13 | //! // ... critical section code | ||
| 14 | //! } | ||
| 14 | //! | 15 | //! |
| 15 | //! Non-blocking: | ||
| 16 | //! if (m.tryLock()) { | 16 | //! if (m.tryLock()) { |
| 17 | //! defer m.unlock(); | 17 | //! defer m.unlock(); |
| 18 | //! // ... critical section | 18 | //! // ... critical section code |
| 19 | //! } else { | ||
| 20 | //! // ... lock not acquired | ||
| 21 | //! } | 19 | //! } |
| 20 | //! ``` | ||
| 22 | 21 | ||
| 23 | impl: Impl = .{}, | ||
| 24 | |||
| 25 | const Mutex = @This(); | ||
| 26 | const std = @import("../std.zig"); | 22 | const std = @import("../std.zig"); |
| 27 | const builtin = @import("builtin"); | 23 | const builtin = @import("builtin"); |
| 24 | const Mutex = @This(); | ||
| 25 | |||
| 28 | const os = std.os; | 26 | const os = std.os; |
| 29 | const assert = std.debug.assert; | 27 | const assert = std.debug.assert; |
| 30 | const windows = os.windows; | ||
| 31 | const linux = os.linux; | ||
| 32 | const testing = std.testing; | 28 | const testing = std.testing; |
| 33 | const StaticResetEvent = std.thread.StaticResetEvent; | 29 | const Atomic = std.atomic.Atomic; |
| 30 | const Futex = std.Thread.Futex; | ||
| 31 | |||
| 32 | impl: Impl = .{}, | ||
| 34 | 33 | ||
| 35 | /// Try to acquire the mutex without blocking. Returns `false` if the mutex is | 34 | /// Tries to acquire the mutex without blocking the caller's thread. |
| 36 | /// unavailable. Otherwise returns `true`. Call `unlock` on the mutex to release. | 35 | /// Returns `false` if the calling thread would have to block to acquire it. |
| 37 | pub fn tryLock(m: *Mutex) bool { | 36 | /// Otherwise, returns `true` and the caller should `unlock()` the Mutex to release it. |
| 38 | return m.impl.tryLock(); | 37 | pub fn tryLock(self: *Mutex) bool { |
| 38 | return self.impl.tryLock(); | ||
| 39 | } | 39 | } |
| 40 | 40 | ||
| 41 | /// Acquire the mutex. Deadlocks if the mutex is already | 41 | /// Acquires the mutex, blocking the caller's thread until it can. |
| 42 | /// held by the calling thread. | 42 | /// It is undefined behavior if the mutex is already held by the caller's thread. |
| 43 | pub fn lock(m: *Mutex) void { | 43 | /// Once acquired, call `unlock()` on the Mutex to release it. |
| 44 | m.impl.lock(); | 44 | pub fn lock(self: *Mutex) void { |
| 45 | self.impl.lock(); | ||
| 45 | } | 46 | } |
| 46 | 47 | ||
| 47 | pub fn unlock(m: *Mutex) void { | 48 | /// Releases the mutex which was previously acquired with `lock()` or `tryLock()`. |
| 48 | m.impl.unlock(); | 49 | /// It is undefined behavior if the mutex is unlocked from a different thread that it was locked from. |
| 50 | pub fn unlock(self: *Mutex) void { | ||
| 51 | self.impl.unlock(); | ||
| 49 | } | 52 | } |
| 50 | 53 | ||
| 51 | const Impl = if (builtin.single_threaded) | 54 | const Impl = if (builtin.single_threaded) |
| 52 | Dummy | 55 | SingleThreadedImpl |
| 53 | else if (builtin.os.tag == .windows) | 56 | else if (builtin.os.tag == .windows) |
| 54 | WindowsMutex | 57 | WindowsImpl |
| 55 | else if (std.Thread.use_pthreads) | 58 | else if (builtin.os.tag.isDarwin()) |
| 56 | PthreadMutex | 59 | DarwinImpl |
| 57 | else | 60 | else |
| 58 | AtomicMutex; | 61 | FutexImpl; |
| 59 | |||
| 60 | pub const AtomicMutex = struct { | ||
| 61 | state: State = .unlocked, | ||
| 62 | 62 | ||
| 63 | const State = enum(i32) { | 63 | const SingleThreadedImpl = struct { |
| 64 | unlocked, | 64 | is_locked: bool = false, |
| 65 | locked, | ||
| 66 | waiting, | ||
| 67 | }; | ||
| 68 | 65 | ||
| 69 | pub fn tryLock(m: *AtomicMutex) bool { | 66 | fn tryLock(self: *Impl) bool { |
| 70 | return @cmpxchgStrong( | 67 | if (self.is_locked) return false; |
| 71 | State, | 68 | self.is_locked = true; |
| 72 | &m.state, | 69 | return true; |
| 73 | .unlocked, | ||
| 74 | .locked, | ||
| 75 | .Acquire, | ||
| 76 | .Monotonic, | ||
| 77 | ) == null; | ||
| 78 | } | 70 | } |
| 79 | 71 | ||
| 80 | pub fn lock(m: *AtomicMutex) void { | 72 | fn lock(self: *Impl) void { |
| 81 | switch (@atomicRmw(State, &m.state, .Xchg, .locked, .Acquire)) { | 73 | if (!self.tryLock()) { |
| 82 | .unlocked => {}, | 74 | unreachable; // deadlock detected |
| 83 | else => |s| m.lockSlow(s), | ||
| 84 | } | 75 | } |
| 85 | } | 76 | } |
| 86 | 77 | ||
| 87 | pub fn unlock(m: *AtomicMutex) void { | 78 | fn unlock(self: *Impl) void { |
| 88 | switch (@atomicRmw(State, &m.state, .Xchg, .unlocked, .Release)) { | 79 | assert(self.is_locked); |
| 89 | .unlocked => unreachable, | 80 | self.is_locked = false; |
| 90 | .locked => {}, | ||
| 91 | .waiting => m.unlockSlow(), | ||
| 92 | } | ||
| 93 | } | 81 | } |
| 82 | }; | ||
| 94 | 83 | ||
| 95 | fn lockSlow(m: *AtomicMutex, current_state: State) void { | 84 | // SRWLOCK on windows is almost always faster than Futex solution. |
| 96 | @setCold(true); | 85 | // It also implements an efficient Condition with requeue support for us. |
| 97 | var new_state = current_state; | 86 | const WindowsImpl = struct { |
| 98 | 87 | srwlock: os.windows.SRWLOCK = .{}, | |
| 99 | var spin: u8 = 0; | ||
| 100 | while (spin < 100) : (spin += 1) { | ||
| 101 | const state = @cmpxchgWeak( | ||
| 102 | State, | ||
| 103 | &m.state, | ||
| 104 | .unlocked, | ||
| 105 | new_state, | ||
| 106 | .Acquire, | ||
| 107 | .Monotonic, | ||
| 108 | ) orelse return; | ||
| 109 | |||
| 110 | switch (state) { | ||
| 111 | .unlocked => {}, | ||
| 112 | .locked => {}, | ||
| 113 | .waiting => break, | ||
| 114 | } | ||
| 115 | |||
| 116 | var iter = std.math.min(32, spin + 1); | ||
| 117 | while (iter > 0) : (iter -= 1) | ||
| 118 | std.atomic.spinLoopHint(); | ||
| 119 | } | ||
| 120 | 88 | ||
| 121 | new_state = .waiting; | 89 | fn tryLock(self: *Impl) bool { |
| 122 | while (true) { | 90 | return os.windows.kernel32.TryAcquireSRWLockExclusive(&self.srwlock) != os.windows.FALSE; |
| 123 | switch (@atomicRmw(State, &m.state, .Xchg, new_state, .Acquire)) { | ||
| 124 | .unlocked => return, | ||
| 125 | else => {}, | ||
| 126 | } | ||
| 127 | switch (builtin.os.tag) { | ||
| 128 | .linux => { | ||
| 129 | switch (linux.getErrno(linux.futex_wait( | ||
| 130 | @ptrCast(*const i32, &m.state), | ||
| 131 | linux.FUTEX.PRIVATE_FLAG | linux.FUTEX.WAIT, | ||
| 132 | @enumToInt(new_state), | ||
| 133 | null, | ||
| 134 | ))) { | ||
| 135 | .SUCCESS => {}, | ||
| 136 | .INTR => {}, | ||
| 137 | .AGAIN => {}, | ||
| 138 | else => unreachable, | ||
| 139 | } | ||
| 140 | }, | ||
| 141 | else => std.atomic.spinLoopHint(), | ||
| 142 | } | ||
| 143 | } | ||
| 144 | } | 91 | } |
| 145 | 92 | ||
| 146 | fn unlockSlow(m: *AtomicMutex) void { | 93 | fn lock(self: *Impl) void { |
| 147 | @setCold(true); | 94 | os.windows.kernel32.AcquireSRWLockExclusive(&self.srwlock); |
| 95 | } | ||
| 148 | 96 | ||
| 149 | switch (builtin.os.tag) { | 97 | fn unlock(self: *Impl) void { |
| 150 | .linux => { | 98 | os.windows.kernel32.ReleaseSRWLockExclusive(&self.srwlock); |
| 151 | switch (linux.getErrno(linux.futex_wake( | ||
| 152 | @ptrCast(*const i32, &m.state), | ||
| 153 | linux.FUTEX.PRIVATE_FLAG | linux.FUTEX.WAKE, | ||
| 154 | 1, | ||
| 155 | ))) { | ||
| 156 | .SUCCESS => {}, | ||
| 157 | .FAULT => unreachable, // invalid pointer passed to futex_wake | ||
| 158 | else => unreachable, | ||
| 159 | } | ||
| 160 | }, | ||
| 161 | else => {}, | ||
| 162 | } | ||
| 163 | } | 99 | } |
| 164 | }; | 100 | }; |
| 165 | 101 | ||
| 166 | pub const PthreadMutex = struct { | 102 | // os_unfair_lock on darwin supports priority inheritance and is generally faster than Futex solutions. |
| 167 | pthread_mutex: std.c.pthread_mutex_t = .{}, | 103 | const DarwinImpl = struct { |
| 104 | oul: os.darwin.os_unfair_lock = .{}, | ||
| 168 | 105 | ||
| 169 | /// Try to acquire the mutex without blocking. Returns true if | 106 | fn tryLock(self: *Impl) bool { |
| 170 | /// the mutex is unavailable. Otherwise returns false. Call | 107 | return os.darwin.os_unfair_lock_trylock(&self.oul); |
| 171 | /// release when done. | ||
| 172 | pub fn tryLock(m: *PthreadMutex) bool { | ||
| 173 | return std.c.pthread_mutex_trylock(&m.pthread_mutex) == .SUCCESS; | ||
| 174 | } | 108 | } |
| 175 | 109 | ||
| 176 | /// Acquire the mutex. Will deadlock if the mutex is already | 110 | fn lock(self: *Impl) void { |
| 177 | /// held by the calling thread. | 111 | os.darwin.os_unfair_lock_lock(&self.oul); |
| 178 | pub fn lock(m: *PthreadMutex) void { | ||
| 179 | switch (std.c.pthread_mutex_lock(&m.pthread_mutex)) { | ||
| 180 | .SUCCESS => {}, | ||
| 181 | .INVAL => unreachable, | ||
| 182 | .BUSY => unreachable, | ||
| 183 | .AGAIN => unreachable, | ||
| 184 | .DEADLK => unreachable, | ||
| 185 | .PERM => unreachable, | ||
| 186 | else => unreachable, | ||
| 187 | } | ||
| 188 | } | 112 | } |
| 189 | 113 | ||
| 190 | pub fn unlock(m: *PthreadMutex) void { | 114 | fn unlock(self: *Impl) void { |
| 191 | switch (std.c.pthread_mutex_unlock(&m.pthread_mutex)) { | 115 | os.darwin.os_unfair_lock_unlock(&self.oul); |
| 192 | .SUCCESS => return, | ||
| 193 | .INVAL => unreachable, | ||
| 194 | .AGAIN => unreachable, | ||
| 195 | .PERM => unreachable, | ||
| 196 | else => unreachable, | ||
| 197 | } | ||
| 198 | } | 116 | } |
| 199 | }; | 117 | }; |
| 200 | 118 | ||
| 201 | /// This has the sematics as `Mutex`, however it does not actually do any | 119 | const FutexImpl = struct { |
| 202 | /// synchronization. Operations are safety-checked no-ops. | 120 | state: Atomic(u32) = Atomic(u32).init(unlocked), |
| 203 | pub const Dummy = struct { | ||
| 204 | locked: @TypeOf(lock_init) = lock_init, | ||
| 205 | 121 | ||
| 206 | const lock_init = if (std.debug.runtime_safety) false else {}; | 122 | const unlocked = 0b00; |
| 123 | const locked = 0b01; | ||
| 124 | const contended = 0b11; // must contain the `locked` bit for x86 optimization below | ||
| 125 | |||
| 126 | fn tryLock(self: *Impl) bool { | ||
| 127 | // Lock with compareAndSwap instead of tryCompareAndSwap to avoid reporting spurious CAS failure. | ||
| 128 | return self.lockFast("compareAndSwap"); | ||
| 129 | } | ||
| 207 | 130 | ||
| 208 | /// Try to acquire the mutex without blocking. Returns false if | 131 | fn lock(self: *Impl) void { |
| 209 | /// the mutex is unavailable. Otherwise returns true. | 132 | // Lock with tryCompareAndSwap instead of compareAndSwap due to being more inline-able on LL/SC archs like ARM. |
| 210 | pub fn tryLock(m: *Dummy) bool { | 133 | if (!self.lockFast("tryCompareAndSwap")) { |
| 211 | if (std.debug.runtime_safety) { | 134 | self.lockSlow(); |
| 212 | if (m.locked) return false; | ||
| 213 | m.locked = true; | ||
| 214 | } | 135 | } |
| 215 | return true; | ||
| 216 | } | 136 | } |
| 217 | 137 | ||
| 218 | /// Acquire the mutex. Will deadlock if the mutex is already | 138 | inline fn lockFast(self: *Impl, comptime casFn: []const u8) bool { |
| 219 | /// held by the calling thread. | 139 | // On x86, use `lock bts` instead of `lock cmpxchg` as: |
| 220 | pub fn lock(m: *Dummy) void { | 140 | // - they both seem to mark the cache-line as modified regardless: https://stackoverflow.com/a/63350048 |
| 221 | if (!m.tryLock()) { | 141 | // - `lock bts` is smaller instruction-wise which makes it better for inlining |
| 222 | @panic("deadlock detected"); | 142 | if (comptime builtin.target.cpu.arch.isX86()) { |
| 143 | const locked_bit = @ctz(u32, @as(u32, locked)); | ||
| 144 | return self.state.bitSet(locked_bit, .Acquire) == 0; | ||
| 223 | } | 145 | } |
| 146 | |||
| 147 | // Acquire barrier ensures grabbing the lock happens before the critical section | ||
| 148 | // and that the previous lock holder's critical section happens before we grab the lock. | ||
| 149 | return @field(self.state, casFn)(unlocked, locked, .Acquire, .Monotonic) == null; | ||
| 224 | } | 150 | } |
| 225 | 151 | ||
| 226 | pub fn unlock(m: *Dummy) void { | 152 | fn lockSlow(self: *Impl) void { |
| 227 | if (std.debug.runtime_safety) { | 153 | @setCold(true); |
| 228 | m.locked = false; | 154 | |
| 155 | // Avoid doing an atomic swap below if we already know the state is contended. | ||
| 156 | // An atomic swap unconditionally stores which marks the cache-line as modified unnecessarily. | ||
| 157 | if (self.state.load(.Monotonic) == contended) { | ||
| 158 | Futex.wait(&self.state, contended); | ||
| 159 | } | ||
| 160 | |||
| 161 | // Try to acquire the lock while also telling the existing lock holder that there are threads waiting. | ||
| 162 | // | ||
| 163 | // Once we sleep on the Futex, we must acquire the mutex using `contended` rather than `locked`. | ||
| 164 | // If not, threads sleeping on the Futex wouldn't see the state change in unlock and potentially deadlock. | ||
| 165 | // The downside is that the last mutex unlocker will see `contended` and do an unnecessary Futex wake | ||
| 166 | // but this is better than having to wake all waiting threads on mutex unlock. | ||
| 167 | // | ||
| 168 | // Acquire barrier ensures grabbing the lock happens before the critical section | ||
| 169 | // and that the previous lock holder's critical section happens before we grab the lock. | ||
| 170 | while (self.state.swap(contended, .Acquire) != unlocked) { | ||
| 171 | Futex.wait(&self.state, contended); | ||
| 172 | } | ||
| 173 | } | ||
| 174 | |||
| 175 | fn unlock(self: *Impl) void { | ||
| 176 | // Unlock the mutex and wake up a waiting thread if any. | ||
| 177 | // | ||
| 178 | // A waiting thread will acquire with `contended` instead of `locked` | ||
| 179 | // which ensures that it wakes up another thread on the next unlock(). | ||
| 180 | // | ||
| 181 | // Release barrier ensures the critical section happens before we let go of the lock | ||
| 182 | // and that our critical section happens before the next lock holder grabs the lock. | ||
| 183 | const state = self.state.swap(unlocked, .Release); | ||
| 184 | assert(state != unlocked); | ||
| 185 | |||
| 186 | if (state == contended) { | ||
| 187 | Futex.wake(&self.state, 1); | ||
| 229 | } | 188 | } |
| 230 | } | 189 | } |
| 231 | }; | 190 | }; |
| 232 | 191 | ||
| 233 | pub const WindowsMutex = struct { | 192 | test "Mutex - smoke test" { |
| 234 | srwlock: windows.SRWLOCK = windows.SRWLOCK_INIT, | 193 | var mutex = Mutex{}; |
| 194 | |||
| 195 | try testing.expect(mutex.tryLock()); | ||
| 196 | try testing.expect(!mutex.tryLock()); | ||
| 197 | mutex.unlock(); | ||
| 235 | 198 | ||
| 236 | pub fn tryLock(m: *WindowsMutex) bool { | 199 | mutex.lock(); |
| 237 | return windows.kernel32.TryAcquireSRWLockExclusive(&m.srwlock) != windows.FALSE; | 200 | try testing.expect(!mutex.tryLock()); |
| 238 | } | 201 | mutex.unlock(); |
| 202 | } | ||
| 203 | |||
| 204 | // A counter which is incremented without atomic instructions | ||
| 205 | const NonAtomicCounter = struct { | ||
| 206 | // direct u128 could maybe use xmm ops on x86 which are atomic | ||
| 207 | value: [2]u64 = [_]u64{ 0, 0 }, | ||
| 239 | 208 | ||
| 240 | pub fn lock(m: *WindowsMutex) void { | 209 | fn get(self: NonAtomicCounter) u128 { |
| 241 | windows.kernel32.AcquireSRWLockExclusive(&m.srwlock); | 210 | return @bitCast(u128, self.value); |
| 242 | } | 211 | } |
| 243 | 212 | ||
| 244 | pub fn unlock(m: *WindowsMutex) void { | 213 | fn inc(self: *NonAtomicCounter) void { |
| 245 | windows.kernel32.ReleaseSRWLockExclusive(&m.srwlock); | 214 | for (@bitCast([2]u64, self.get() + 1)) |v, i| { |
| 215 | @ptrCast(*volatile u64, &self.value[i]).* = v; | ||
| 216 | } | ||
| 246 | } | 217 | } |
| 247 | }; | 218 | }; |
| 248 | 219 | ||
| 249 | const TestContext = struct { | 220 | test "Mutex - many uncontended" { |
| 250 | mutex: *Mutex, | 221 | // This test requires spawning threads. |
| 251 | data: i128, | 222 | if (builtin.single_threaded) { |
| 223 | return error.SkipZigTest; | ||
| 224 | } | ||
| 225 | |||
| 226 | const num_threads = 4; | ||
| 227 | const num_increments = 1000; | ||
| 252 | 228 | ||
| 253 | const incr_count = 10000; | 229 | const Runner = struct { |
| 254 | }; | 230 | mutex: Mutex = .{}, |
| 231 | thread: std.Thread = undefined, | ||
| 232 | counter: NonAtomicCounter = .{}, | ||
| 255 | 233 | ||
| 256 | test "basic usage" { | 234 | fn run(self: *@This()) void { |
| 257 | var mutex = Mutex{}; | 235 | var i: usize = num_increments; |
| 236 | while (i > 0) : (i -= 1) { | ||
| 237 | self.mutex.lock(); | ||
| 238 | defer self.mutex.unlock(); | ||
| 258 | 239 | ||
| 259 | var context = TestContext{ | 240 | self.counter.inc(); |
| 260 | .mutex = &mutex, | 241 | } |
| 261 | .data = 0, | 242 | } |
| 262 | }; | 243 | }; |
| 263 | 244 | ||
| 245 | var runners = [_]Runner{.{}} ** num_threads; | ||
| 246 | for (runners) |*r| r.thread = try std.Thread.spawn(.{}, Runner.run, .{r}); | ||
| 247 | for (runners) |r| r.thread.join(); | ||
| 248 | for (runners) |r| try testing.expectEqual(r.counter.get(), num_increments); | ||
| 249 | } | ||
| 250 | |||
| 251 | test "Mutex - many contended" { | ||
| 252 | // This test requires spawning threads. | ||
| 264 | if (builtin.single_threaded) { | 253 | if (builtin.single_threaded) { |
| 265 | worker(&context); | 254 | return error.SkipZigTest; |
| 266 | try testing.expect(context.data == TestContext.incr_count); | 255 | } |
| 267 | } else { | 256 | |
| 268 | const thread_count = 10; | 257 | const num_threads = 4; |
| 269 | var threads: [thread_count]std.Thread = undefined; | 258 | const num_increments = 1000; |
| 270 | for (threads) |*t| { | 259 | |
| 271 | t.* = try std.Thread.spawn(.{}, worker, .{&context}); | 260 | const Runner = struct { |
| 261 | mutex: Mutex = .{}, | ||
| 262 | counter: NonAtomicCounter = .{}, | ||
| 263 | |||
| 264 | fn run(self: *@This()) void { | ||
| 265 | var i: usize = num_increments; | ||
| 266 | while (i > 0) : (i -= 1) { | ||
| 267 | // Occasionally hint to let another thread run. | ||
| 268 | defer if (i % 100 == 0) std.Thread.yield() catch {}; | ||
| 269 | |||
| 270 | self.mutex.lock(); | ||
| 271 | defer self.mutex.unlock(); | ||
| 272 | |||
| 273 | self.counter.inc(); | ||
| 274 | } | ||
| 272 | } | 275 | } |
| 273 | for (threads) |t| | 276 | }; |
| 274 | t.join(); | ||
| 275 | 277 | ||
| 276 | try testing.expect(context.data == thread_count * TestContext.incr_count); | 278 | var runner = Runner{}; |
| 277 | } | ||
| 278 | } | ||
| 279 | 279 | ||
| 280 | fn worker(ctx: *TestContext) void { | 280 | var threads: [num_threads]std.Thread = undefined; |
| 281 | var i: usize = 0; | 281 | for (threads) |*t| t.* = try std.Thread.spawn(.{}, Runner.run, .{&runner}); |
| 282 | while (i != TestContext.incr_count) : (i += 1) { | 282 | for (threads) |t| t.join(); |
| 283 | ctx.mutex.lock(); | ||
| 284 | defer ctx.mutex.unlock(); | ||
| 285 | 283 | ||
| 286 | ctx.data += 1; | 284 | try testing.expectEqual(runner.counter.get(), num_increments * num_threads); |
| 287 | } | ||
| 288 | } | 285 | } |
lib/std/atomic/Atomic.zig+87-81| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | const std = @import("../std.zig"); | 1 | const std = @import("../std.zig"); |
| 2 | const builtin = @import("builtin"); | ||
| 2 | 3 | ||
| 3 | const testing = std.testing; | 4 | const testing = std.testing; |
| 4 | const target = @import("builtin").target; | ||
| 5 | const Ordering = std.atomic.Ordering; | 5 | const Ordering = std.atomic.Ordering; |
| 6 | 6 | ||
| 7 | pub fn Atomic(comptime T: type) type { | 7 | pub fn Atomic(comptime T: type) type { |
| ... | @@ -164,87 +164,13 @@ pub fn Atomic(comptime T: type) type { | ... | @@ -164,87 +164,13 @@ pub fn Atomic(comptime T: type) type { |
| 164 | return bitRmw(self, .Toggle, bit, ordering); | 164 | return bitRmw(self, .Toggle, bit, ordering); |
| 165 | } | 165 | } |
| 166 | 166 | ||
| 167 | inline fn bitRmw( | 167 | inline fn bitRmw(self: *Self, comptime op: BitRmwOp, bit: Bit, comptime ordering: Ordering) u1 { |
| 168 | self: *Self, | ||
| 169 | comptime op: BitRmwOp, | ||
| 170 | bit: Bit, | ||
| 171 | comptime ordering: Ordering, | ||
| 172 | ) u1 { | ||
| 173 | // x86 supports dedicated bitwise instructions | 168 | // x86 supports dedicated bitwise instructions |
| 174 | if (comptime target.cpu.arch.isX86() and @sizeOf(T) >= 2 and @sizeOf(T) <= 8) { | 169 | if (comptime builtin.target.cpu.arch.isX86() and @sizeOf(T) >= 2 and @sizeOf(T) <= 8) { |
| 175 | const old_bit: u8 = switch (@sizeOf(T)) { | 170 | // TODO: stage2 currently doesn't like the inline asm this function emits. |
| 176 | 2 => switch (op) { | 171 | if (builtin.zig_backend == .stage1) { |
| 177 | .Set => asm volatile ("lock btsw %[bit], %[ptr]" | 172 | return x86BitRmw(self, op, bit, ordering); |
| 178 | // LLVM doesn't support u1 flag register return values | 173 | } |
| 179 | : [result] "={@ccc}" (-> u8), | ||
| 180 | : [ptr] "*p" (&self.value), | ||
| 181 | [bit] "X" (@as(T, bit)), | ||
| 182 | : "cc", "memory" | ||
| 183 | ), | ||
| 184 | .Reset => asm volatile ("lock btrw %[bit], %[ptr]" | ||
| 185 | // LLVM doesn't support u1 flag register return values | ||
| 186 | : [result] "={@ccc}" (-> u8), | ||
| 187 | : [ptr] "*p" (&self.value), | ||
| 188 | [bit] "X" (@as(T, bit)), | ||
| 189 | : "cc", "memory" | ||
| 190 | ), | ||
| 191 | .Toggle => asm volatile ("lock btcw %[bit], %[ptr]" | ||
| 192 | // LLVM doesn't support u1 flag register return values | ||
| 193 | : [result] "={@ccc}" (-> u8), | ||
| 194 | : [ptr] "*p" (&self.value), | ||
| 195 | [bit] "X" (@as(T, bit)), | ||
| 196 | : "cc", "memory" | ||
| 197 | ), | ||
| 198 | }, | ||
| 199 | 4 => switch (op) { | ||
| 200 | .Set => asm volatile ("lock btsl %[bit], %[ptr]" | ||
| 201 | // LLVM doesn't support u1 flag register return values | ||
| 202 | : [result] "={@ccc}" (-> u8), | ||
| 203 | : [ptr] "*p" (&self.value), | ||
| 204 | [bit] "X" (@as(T, bit)), | ||
| 205 | : "cc", "memory" | ||
| 206 | ), | ||
| 207 | .Reset => asm volatile ("lock btrl %[bit], %[ptr]" | ||
| 208 | // LLVM doesn't support u1 flag register return values | ||
| 209 | : [result] "={@ccc}" (-> u8), | ||
| 210 | : [ptr] "*p" (&self.value), | ||
| 211 | [bit] "X" (@as(T, bit)), | ||
| 212 | : "cc", "memory" | ||
| 213 | ), | ||
| 214 | .Toggle => asm volatile ("lock btcl %[bit], %[ptr]" | ||
| 215 | // LLVM doesn't support u1 flag register return values | ||
| 216 | : [result] "={@ccc}" (-> u8), | ||
| 217 | : [ptr] "*p" (&self.value), | ||
| 218 | [bit] "X" (@as(T, bit)), | ||
| 219 | : "cc", "memory" | ||
| 220 | ), | ||
| 221 | }, | ||
| 222 | 8 => switch (op) { | ||
| 223 | .Set => asm volatile ("lock btsq %[bit], %[ptr]" | ||
| 224 | // LLVM doesn't support u1 flag register return values | ||
| 225 | : [result] "={@ccc}" (-> u8), | ||
| 226 | : [ptr] "*p" (&self.value), | ||
| 227 | [bit] "X" (@as(T, bit)), | ||
| 228 | : "cc", "memory" | ||
| 229 | ), | ||
| 230 | .Reset => asm volatile ("lock btrq %[bit], %[ptr]" | ||
| 231 | // LLVM doesn't support u1 flag register return values | ||
| 232 | : [result] "={@ccc}" (-> u8), | ||
| 233 | : [ptr] "*p" (&self.value), | ||
| 234 | [bit] "X" (@as(T, bit)), | ||
| 235 | : "cc", "memory" | ||
| 236 | ), | ||
| 237 | .Toggle => asm volatile ("lock btcq %[bit], %[ptr]" | ||
| 238 | // LLVM doesn't support u1 flag register return values | ||
| 239 | : [result] "={@ccc}" (-> u8), | ||
| 240 | : [ptr] "*p" (&self.value), | ||
| 241 | [bit] "X" (@as(T, bit)), | ||
| 242 | : "cc", "memory" | ||
| 243 | ), | ||
| 244 | }, | ||
| 245 | else => @compileError("Invalid atomic type " ++ @typeName(T)), | ||
| 246 | }; | ||
| 247 | return @intCast(u1, old_bit); | ||
| 248 | } | 174 | } |
| 249 | 175 | ||
| 250 | const mask = @as(T, 1) << bit; | 176 | const mask = @as(T, 1) << bit; |
| ... | @@ -256,6 +182,86 @@ pub fn Atomic(comptime T: type) type { | ... | @@ -256,6 +182,86 @@ pub fn Atomic(comptime T: type) type { |
| 256 | 182 | ||
| 257 | return @boolToInt(value & mask != 0); | 183 | return @boolToInt(value & mask != 0); |
| 258 | } | 184 | } |
| 185 | |||
| 186 | inline fn x86BitRmw(self: *Self, comptime op: BitRmwOp, bit: Bit, comptime ordering: Ordering) u1 { | ||
| 187 | const old_bit: u8 = switch (@sizeOf(T)) { | ||
| 188 | 2 => switch (op) { | ||
| 189 | .Set => asm volatile ("lock btsw %[bit], %[ptr]" | ||
| 190 | // LLVM doesn't support u1 flag register return values | ||
| 191 | : [result] "={@ccc}" (-> u8), | ||
| 192 | : [ptr] "*p" (&self.value), | ||
| 193 | [bit] "X" (@as(T, bit)), | ||
| 194 | : "cc", "memory" | ||
| 195 | ), | ||
| 196 | .Reset => asm volatile ("lock btrw %[bit], %[ptr]" | ||
| 197 | // LLVM doesn't support u1 flag register return values | ||
| 198 | : [result] "={@ccc}" (-> u8), | ||
| 199 | : [ptr] "*p" (&self.value), | ||
| 200 | [bit] "X" (@as(T, bit)), | ||
| 201 | : "cc", "memory" | ||
| 202 | ), | ||
| 203 | .Toggle => asm volatile ("lock btcw %[bit], %[ptr]" | ||
| 204 | // LLVM doesn't support u1 flag register return values | ||
| 205 | : [result] "={@ccc}" (-> u8), | ||
| 206 | : [ptr] "*p" (&self.value), | ||
| 207 | [bit] "X" (@as(T, bit)), | ||
| 208 | : "cc", "memory" | ||
| 209 | ), | ||
| 210 | }, | ||
| 211 | 4 => switch (op) { | ||
| 212 | .Set => asm volatile ("lock btsl %[bit], %[ptr]" | ||
| 213 | // LLVM doesn't support u1 flag register return values | ||
| 214 | : [result] "={@ccc}" (-> u8), | ||
| 215 | : [ptr] "*p" (&self.value), | ||
| 216 | [bit] "X" (@as(T, bit)), | ||
| 217 | : "cc", "memory" | ||
| 218 | ), | ||
| 219 | .Reset => asm volatile ("lock btrl %[bit], %[ptr]" | ||
| 220 | // LLVM doesn't support u1 flag register return values | ||
| 221 | : [result] "={@ccc}" (-> u8), | ||
| 222 | : [ptr] "*p" (&self.value), | ||
| 223 | [bit] "X" (@as(T, bit)), | ||
| 224 | : "cc", "memory" | ||
| 225 | ), | ||
| 226 | .Toggle => asm volatile ("lock btcl %[bit], %[ptr]" | ||
| 227 | // LLVM doesn't support u1 flag register return values | ||
| 228 | : [result] "={@ccc}" (-> u8), | ||
| 229 | : [ptr] "*p" (&self.value), | ||
| 230 | [bit] "X" (@as(T, bit)), | ||
| 231 | : "cc", "memory" | ||
| 232 | ), | ||
| 233 | }, | ||
| 234 | 8 => switch (op) { | ||
| 235 | .Set => asm volatile ("lock btsq %[bit], %[ptr]" | ||
| 236 | // LLVM doesn't support u1 flag register return values | ||
| 237 | : [result] "={@ccc}" (-> u8), | ||
| 238 | : [ptr] "*p" (&self.value), | ||
| 239 | [bit] "X" (@as(T, bit)), | ||
| 240 | : "cc", "memory" | ||
| 241 | ), | ||
| 242 | .Reset => asm volatile ("lock btrq %[bit], %[ptr]" | ||
| 243 | // LLVM doesn't support u1 flag register return values | ||
| 244 | : [result] "={@ccc}" (-> u8), | ||
| 245 | : [ptr] "*p" (&self.value), | ||
| 246 | [bit] "X" (@as(T, bit)), | ||
| 247 | : "cc", "memory" | ||
| 248 | ), | ||
| 249 | .Toggle => asm volatile ("lock btcq %[bit], %[ptr]" | ||
| 250 | // LLVM doesn't support u1 flag register return values | ||
| 251 | : [result] "={@ccc}" (-> u8), | ||
| 252 | : [ptr] "*p" (&self.value), | ||
| 253 | [bit] "X" (@as(T, bit)), | ||
| 254 | : "cc", "memory" | ||
| 255 | ), | ||
| 256 | }, | ||
| 257 | else => @compileError("Invalid atomic type " ++ @typeName(T)), | ||
| 258 | }; | ||
| 259 | |||
| 260 | // TODO: emit appropriate tsan fence if compiling with tsan | ||
| 261 | _ = ordering; | ||
| 262 | |||
| 263 | return @intCast(u1, old_bit); | ||
| 264 | } | ||
| 259 | }); | 265 | }); |
| 260 | }; | 266 | }; |
| 261 | } | 267 | } |
lib/std/heap/general_purpose_allocator.zig+8-3| ... | @@ -151,12 +151,12 @@ pub const Config = struct { | ... | @@ -151,12 +151,12 @@ pub const Config = struct { |
| 151 | 151 | ||
| 152 | /// What type of mutex you'd like to use, for thread safety. | 152 | /// What type of mutex you'd like to use, for thread safety. |
| 153 | /// when specfied, the mutex type must have the same shape as `std.Thread.Mutex` and | 153 | /// when specfied, the mutex type must have the same shape as `std.Thread.Mutex` and |
| 154 | /// `std.Thread.Mutex.Dummy`, and have no required fields. Specifying this field causes | 154 | /// `DummyMutex`, and have no required fields. Specifying this field causes |
| 155 | /// the `thread_safe` field to be ignored. | 155 | /// the `thread_safe` field to be ignored. |
| 156 | /// | 156 | /// |
| 157 | /// when null (default): | 157 | /// when null (default): |
| 158 | /// * the mutex type defaults to `std.Thread.Mutex` when thread_safe is enabled. | 158 | /// * the mutex type defaults to `std.Thread.Mutex` when thread_safe is enabled. |
| 159 | /// * the mutex type defaults to `std.Thread.Mutex.Dummy` otherwise. | 159 | /// * the mutex type defaults to `DummyMutex` otherwise. |
| 160 | MutexType: ?type = null, | 160 | MutexType: ?type = null, |
| 161 | 161 | ||
| 162 | /// This is a temporary debugging trick you can use to turn segfaults into more helpful | 162 | /// This is a temporary debugging trick you can use to turn segfaults into more helpful |
| ... | @@ -198,7 +198,12 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { | ... | @@ -198,7 +198,12 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { |
| 198 | else if (config.thread_safe) | 198 | else if (config.thread_safe) |
| 199 | std.Thread.Mutex{} | 199 | std.Thread.Mutex{} |
| 200 | else | 200 | else |
| 201 | std.Thread.Mutex.Dummy{}; | 201 | DummyMutex{}; |
| 202 | |||
| 203 | const DummyMutex = struct { | ||
| 204 | fn lock(_: *DummyMutex) void {} | ||
| 205 | fn unlock(_: *DummyMutex) void {} | ||
| 206 | }; | ||
| 202 | 207 | ||
| 203 | const stack_n = config.stack_trace_frames; | 208 | const stack_n = config.stack_trace_frames; |
| 204 | const one_trace_size = @sizeOf(usize) * stack_n; | 209 | const one_trace_size = @sizeOf(usize) * stack_n; |
lib/std/os/windows.zig+9-4| ... | @@ -3680,10 +3680,15 @@ pub const OBJECT_NAME_INFORMATION = extern struct { | ... | @@ -3680,10 +3680,15 @@ pub const OBJECT_NAME_INFORMATION = extern struct { |
| 3680 | Name: UNICODE_STRING, | 3680 | Name: UNICODE_STRING, |
| 3681 | }; | 3681 | }; |
| 3682 | 3682 | ||
| 3683 | pub const SRWLOCK = usize; | 3683 | pub const SRWLOCK_INIT = SRWLOCK{}; |
| 3684 | pub const SRWLOCK_INIT: SRWLOCK = 0; | 3684 | pub const SRWLOCK = extern struct { |
| 3685 | pub const CONDITION_VARIABLE = usize; | 3685 | Ptr: ?PVOID = null, |
| 3686 | pub const CONDITION_VARIABLE_INIT: CONDITION_VARIABLE = 0; | 3686 | }; |
| 3687 | |||
| 3688 | pub const CONDITION_VARIABLE_INIT = CONDITION_VARIABLE{}; | ||
| 3689 | pub const CONDITION_VARIABLE = extern struct { | ||
| 3690 | Ptr: ?PVOID = null, | ||
| 3691 | }; | ||
| 3687 | 3692 | ||
| 3688 | pub const FILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 0x1; | 3693 | pub const FILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 0x1; |
| 3689 | pub const FILE_SKIP_SET_EVENT_ON_HANDLE = 0x2; | 3694 | pub const FILE_SKIP_SET_EVENT_ON_HANDLE = 0x2; |