aboutsummaryrefslogtreecommitdiff
path: root/lib/std/spinlock.zig
blob: bd811f709cbf46cdb367c4851c248ed8b118d772 (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
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

    pub const Held = struct {
        spinlock: *SpinLock,

        pub fn release(self: Held) void {
            @atomicStore(u8, &self.spinlock.lock, 0, .Release);
        }
    };

    pub fn init() SpinLock {
        return SpinLock{ .lock = 0 };
    }

    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 };
    }

    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),
            }
        }
    }

    /// 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();
    const held = lock.acquire();
    defer held.release();
}