aboutsummaryrefslogtreecommitdiff
path: root/lib/std/spinlock.zig
blob: 0af08e9a84f77ab7516c28c710c8ccc4022a1aa0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
const std = @import("std.zig");
const builtin = @import("builtin");

pub const SpinLock = struct {
    state: State,

    const State = enum(u8) {
        Unlocked,
        Locked,
    };

    pub const Held = struct {
        spinlock: *SpinLock,

        pub fn release(self: Held) void {
            @atomicStore(State, &self.spinlock.state, .Unlocked, .Release);
        }
    };

    pub fn init() SpinLock {
        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 {
        while (true) {
            return self.tryAcquire() orelse {
                yield();
                continue;
            };
        }
    }

    pub fn yield() void {
        // 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.tag) {
            .windows => loopHint(400),
            else => std.os.sched_yield() catch loopHint(1),
        }
    }

    /// Hint to the cpu that execution is spinning
    /// for the given amount of iterations.
    pub fn loopHint(iterations: usize) void {
        var i = iterations;
        while (i != 0) : (i -= 1) {
            switch (builtin.arch) {
                // these instructions use a memory clobber as they
                // flush the pipeline of any speculated reads/writes.
                .i386, .x86_64 => asm volatile ("pause"
                    :
                    :
                    : "memory"
                ),
                .arm, .aarch64 => asm volatile ("yield"
                    :
                    :
                    : "memory"
                ),
                else => std.os.sched_yield() catch {},
            }
        }
    }
};

test "spinlock" {
    var lock = SpinLock.init();
    defer lock.deinit();

    const held = lock.acquire();
    defer held.release();
}