一句话描述
任何软件工程遇到的问题都可以通过增加一个中间层来解决
Rust 场景
多个线程访问同一个数据并需要修改,需要保证: 1.同一时间,数据只能被一个线程所占用。 2.使用完之后,能正确的释放占用。
你会如何设计?
先创建一个结构体,能满足标记当前数据状态:
pub struct SpinLock {
// 用原子bool,因为我们场景时多线程交互
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);
}
}
上面的代码,实现了一个安全的逻辑 加锁、解锁,然而并没什么用,它并未包含任何要保护的内容!现在它加上它要保护的内容:
use std::cell::UnsafeCell;
pub struct SpinLock<T> {
locked: AtomicBool,
value: UnsafeCell<T>,
}
关于 Rust 利用 Send
和 Sync
特征来表示数据能够安全的在线程中 发送
和 共享
这件事大家都知道。
UnsafeCell
已经实现了 Send
特征, 但没有实现 Sync
特征, 需要我们为 SpinLock 实现 Sync
,让编译器知道 SpinLock 这个类型,在线程之间共享是安全的:
unsafe impl<T> Sync for SpinLock<T> where T: Send {}
现在需要修改下构造函数和 获得锁、解锁:
impl<T> SpinLock<T> {
pub const fn new(value: T) -> Self {
Self {
locked: AtomicBool::new(false),
value: UnsafeCell::new(value),
}
}
pub fn lock(&self) -> &mut T {
while self.locked.swap(true, Acquire) {
std::hint::spin_loop();
}
// 返回一个 &mut T
unsafe { &mut *self.value.get() }
}
}
等等… 遇到一个问题, lock 把保护的数据放出去了! 但是当 unlock 的时候,如何把放出的数据收回来?
还记得本文的标题么
加一个中间层的思想真妙
新建一个类型,由它来接管保护类型的收和放,它的行为类似于引用,但也实现了 Drop 特性以在它被删除时执行某些操作
这种类型通常称为 守卫(guard)
// 加 `a 生命周期,保证了 Guard 不会比 SpinLock 生命周期长
pub struct Guard<'a, T> {
lock: &'a SpinLock<T>,
}
然后再修改 lock 方法:
pub fn lock(&self) -> Guard<T> {
while self.locked.swap(true, Acquire) {
std::hint::spin_loop();
}
// 返回这个守卫 (多加的一层)
Guard { lock: self }
}
Guard
类型没有构造函数并且它的字段是私有的,sync
是用户获得 Guard
的唯一途径,因此,我们可以安全地假设 Guard
的存在意味着 SpinLock
已被锁定。
接着需要完善 Guard
, 让它能表现的很自然的 获得 和 回收保护的内容;
关于 Rust 利用 Deref
和 DerefMut
的特征实现.
语法访问到正确的引用,想必大家都知道,
use std::ops::{Deref, DerefMut};
impl<T> Deref for Guard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
// 安全的获得受保护的数据
unsafe { &*self.lock.value.get() }
}
}
impl<T> DerefMut for Guard<'_, T> {
fn deref_mut(&mut self) -> &mut T {
// 安全的获得受保护的数据
unsafe { &mut *self.lock.value.get() }
}
}
最后 守卫离开作用域,我们要解除锁,想必大家都知道 Drop 特征吧:
impl<T> Drop for Guard<'_, T> {
fn drop(&mut self) {
self.lock.locked.store(false, Release);
}
}
就这样,我们把数据安全的获取 和 回收,交给了 守卫,能感受到 加一个中间层的魅力吗?
上面的代码是 《Rust原子和锁》 的第四章,讲的是如何构建我们自己的自旋锁,看完该章体会到‘思想’的重要性, 它能决定把 工具 使用到那种程度! 你希望你能使用的到那种程度呢?