Spin Lock 自旋锁

原子操作只能保护单个变量,保护多个数据就需要一把锁.自旋锁的机制就是拿不到锁就原地转圈等待,不进内核

我们需要用 UnsafeCell<T> 来包裹数据,唯一合法的内部可变性的来源,允许通过 &self 拿到 &mut self,是很多数据结构 Mutex<T>, RwLock<T> 等的基础.

unsafe impl 则是因为 UnsafeCell 并没有实现 Send, Sync 这两个 trait,手动告诉编译器数据有锁保护.

数据结构定义
1
2
3
4
5
6
7
8
9
10
use core::cell::UnsafeCell;
use core::sync::atomic::{AtomicBool, Ordering};

pub struct SpinLock<T> {
locked: AtomicBool,
data: UnsafeCell<T>
}

unsafe impl<T: Send> Send for SpinLock<T> {}
unsafe impl<T: Sync> Sync for SpinLock<T> {}

接着写 .lock(),就是用 CAS 循环抢锁.spin_loop() 的意思是告诉 CPU 现在没有操作,让 CPU 挂起省电

lock() CAS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pub fn lock(&self) -> &mut T {
while self.locked.compare_exchange(
false, // old
true, // new
Ordering::Acquire,
Ordering::Relaxed,
).is_err() {
core::hint::spin_loop();
}

unsafe { &mut *self.data.get() }
}

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

那如果忘了 unlock() 怎么办?就有可能永久死锁.Rust 的解决办法是通过类型系统的 RAII Guard 自动释放.这就是 Lock Guard 的设计思路.

Lock Guard

Lock Guard 需要实现什么东西来进行自动 lock, unlock 呢?

  • Deref trait: 通过 *guard 读取数据
  • DerefMut trait: 通过 *guard 写数据
  • Drop trait: 离开作用域后,自动 unlock() 释放锁

由于我们需要保证 Guard 不能获得比锁本身还长,所以,我们让 Guard 内部取得 Lock 的引用,并且标记上生命周期.

SpinGuard 数据结构
1
2
3
pub struct SpinGuard<'a, T> {
lock: &'a SpinLock<T>,
}

接着实现必要的 trait

Trait 实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
impl<T> Deref for SpinGuard<'_, T> {
fn deref(&self) -> &T {
unsafe { & *self.lock.data.get() }
}
}

impl<T> DerefMut for SpinGuard<'_, T> {
fn deref_mut(&mut self) ->&mut T {
unsafe { &mut *self.lock.data.get() }
}
}

impl<T> Drop for SpinGuard<'_, T> {
fn drop(&mut self) {
self.lock.locked.store(false, Ordering::Release);
}
}

当我们尝试锁住 Lock 的时候,我们得到一个 LockGuard 对象,通过 LockGuard 与数据进行交互.LockGuard 在被回收的时候释放锁.

Lock.lock()
1
2
3
4
5
6
7
8
9
10
pub fn lock(&self) -> SpinGuard<'_, T> {
while self
.locked
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
core::hint::spin_loop();
}
SpinGuard { lock: self }
}