diff --git a/lib/std/spinlock.zig b/lib/std/spinlock.zig index bd811f709c..ef6ac482b2 100644 --- a/lib/std/spinlock.zig +++ b/lib/std/spinlock.zig @@ -1,69 +1,71 @@ const std = @import("std.zig"); const builtin = @import("builtin"); -const assert = std.debug.assert; -const time = std.time; -const os = std.os; pub const SpinLock = struct { - lock: u8, // TODO use a bool or enum + state: State, + + const State = enum(u8) { + Unlocked, + Locked, + }; pub const Held = struct { spinlock: *SpinLock, pub fn release(self: Held) void { - @atomicStore(u8, &self.spinlock.lock, 0, .Release); + @atomicStore(State, &self.spinlock.state, .Unlocked, .Release); } }; pub fn init() SpinLock { - return SpinLock{ .lock = 0 }; + return SpinLock{ .state = .Unlocked }; + } + + pub fn deinit(self: *SpinLock) void { + self.* = undefined; + } + + pub fn tryAcquire(self: *SpinLock) ?Held { + return switch (@atomicRmw(State, &self.state, .Xchg, .Locked, .Acquire)) { + .Unlocked => Held{ .spinlock = self }, + .Locked => null, + }; } pub fn acquire(self: *SpinLock) Held { - var backoff = Backoff.init(); - while (@atomicRmw(u8, &self.lock, .Xchg, 1, .Acquire) != 0) - backoff.yield(); - return Held{ .spinlock = self }; + while (true) { + return self.tryAcquire() orelse { + // On native windows, SwitchToThread is too expensive, + // and yielding for 380-410 iterations was found to be + // a nice sweet spot. Posix systems on the other hand, + // especially linux, perform better by yielding the thread. + switch (builtin.os) { + .windows => yield(400), + else => std.os.sched_yield() catch yield(1), + } + continue; + }; + } } + /// Hint to the cpu that execution is spinning + /// for the given amount of iterations. pub fn yield(iterations: usize) void { var i = iterations; while (i != 0) : (i -= 1) { switch (builtin.arch) { .i386, .x86_64 => asm volatile ("pause"), .arm, .aarch64 => asm volatile ("yield"), - else => time.sleep(0), + else => std.os.sched_yield() catch {}, } } } - - /// Provides a method to incrementally yield longer each time its called. - pub const Backoff = struct { - iteration: usize, - - pub fn init() @This() { - return @This(){ .iteration = 0 }; - } - - /// Modified hybrid yielding from - /// http://www.1024cores.net/home/lock-free-algorithms/tricks/spinning - pub fn yield(self: *@This()) void { - defer self.iteration +%= 1; - if (self.iteration < 20) { - SpinLock.yield(self.iteration); - } else if (self.iteration < 24) { - os.sched_yield() catch time.sleep(1); - } else if (self.iteration < 26) { - time.sleep(1 * time.millisecond); - } else { - time.sleep(10 * time.millisecond); - } - } - }; }; test "spinlock" { var lock = SpinLock.init(); + defer lock.deinit(); + const held = lock.acquire(); defer held.release(); }