aboutsummaryrefslogtreecommitdiff
path: root/lib/std/SpinLock.zig
blob: a16cfa930bdf0d565b34b4571d8d937e9935924c (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
86
// 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.
//! A mutually exclusive lock that grinds the CPU rather than interacting with
//! the operating system. It does however yield to the OS scheduler while
//! spinning, when targeting an OS that supports it.
//! This struct can be initialized directly and statically initialized. The
//! default state is unlocked.

state: State = State.Unlocked,

const std = @import("std.zig");
const builtin = @import("builtin");
const SpinLock = @This();

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 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 "basic usage" {
    var lock: SpinLock = .{};

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