SpinLock and the Idea of LockGuard
原子操作只能保护单个变量,保护多个数据就需要一把锁.自旋锁的机制就是拿不到锁就原地转圈等待,不进内核.
我们需要用 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() CAS1 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, true, 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, 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 } }
|