自旋锁,就是在尝试获取锁失败时,不直接挂起,而是一直循环尝试获取锁。这在一些锁冲突较小且占用锁时间非常短的场景下非常有用,因为它可以减少相应的系统调用。

接下来我们就使用 Rust 来实现一个自己的自旋锁。

基础版

首先是最基础的版本,我们在 lock 的时候,如果失败了,就一直循环尝试,直到成功获取锁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pub struct SpinLock {
locked: AtomicBool,
}

impl SpinLock {
pub const fn new() -> Self {
Self {
locked: AtomicBool::new(false),
}
}

pub fn lock(&self) {
while self.locked.swap(true, Ordering::Acquire) {
std::hint::spin_loop();
}
}

pub fn unlock(&self) {
self.locked.store(false, Ordering::Release);
}
}

在上述实现中,我们定义了结果 SpinLock,它包含一个 value 和原子变量 locked

lock 方法中,我们尝试对 locked 原子变量进行 swaptrue 的操作,swap 会返回交换之前的值,如果是 false,那就说明抢锁成功了,这个时候 lock 就成功返回,否则,则调用 std::hint::spin_loop() 进行自选,在下一次 while 循环中再尝试获取锁。

unlock 方法中,我们只需要将 locked 设置为 false 即可。

示例图如下:

这里有 2 个需要关注的点:

  1. std::hint::spin_loop() 会向 CPU 发送特定指令(如 x86 的 pause 或 ARM 的 yield),提示当前处于忙等待状态。这允许 CPU 优化执行行为,例如:
    • 降低功耗:减少自旋期间的计算资源消耗。
    • 提升多线程效率:在超线程架构中,避免单个核心的忙等待阻塞其他线程的执行。
  2. 这里我们内存循序使用了一对 AcquireRelease。其中:
    • Acquire 是为了当前线程可以看到其他线程对 locked 的写结果,即看到当前 locked 真正的值。
    • Release 是为了当前线程对 locked写的结果对其他线程可见,即释放当前 locked 真正的值给其他线程。

unsafe 版本