构建自己的自旋锁
请求锁定常规互斥锁时, 若该锁已经处于锁定状态, 当成线程将进入睡眠状态, 等待操作系统的唤醒.
然而操作系统并不会在该锁被释放后立即唤醒线程, 所以这种方法会引入一定的延迟.
自旋锁 (spin lock) 是一种专门用于解决该问题的互斥锁. 这种锁不会让线程进入休眠状态, 而是会不断尝试上锁. 一旦锁被释放, 就会立即尝试锁定该锁.
由于需要不断的轮询, 这种方法会增加处理器的负担.
最小实现
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, Acquire) {
std::hint::spin_loop();
}
}
pub fn unlock(&self) {
self.locked.store(false, Release);
}
}
该实现可以确保在 unlock
(Release) 和 lock
(Acquire) 之间建立先行发生关系, 防止被锁保护的数据出现竞争.
其中 std::hint::spin_loop()
用于生成特殊的指令, 使处理器可以针对这种特殊用例做优化.
与 C++ 中的锁相似, 上面实现的锁并没有明确被保护的数据, 因此编译器无法判断上锁期间是否只访问了被保护的数据.
在此期间, 通过可变引用访问这些数据依然需要编写 unsafe 代码, 且需要人工检查是否只访问了被保护的数据.
这个问题可以通过让锁持有被保护的数据解决, 在编写锁的内部使用 unsafe 代码, 而不是任何使用了锁的地方.
与原子类型相同, 锁本身是不通过 mut
修饰的, 这样才能被不同线程共享. 但同时又需要所持有的数据是可变的, 这就需要借助内部可变性来实现:
因为 UnsafeCell
实现了 !Sync
, 所以 SpinLock
默认不会实现 Sync
, 需要手动为其添加实现:
主动为类型添加自动 trait 通常意味着该复合类型中有类型不满足此 trait, 因此是 unsafe 代码.