加一个中间层的思想真妙

Static blog


一句话描述

任何软件工程遇到的问题都可以通过增加一个中间层来解决

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 利用 SendSync 特征来表示数据能够安全的在线程中 发送共享 这件事大家都知道。

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 利用 DerefDerefMut 的特征实现.语法访问到正确的引用,想必大家都知道,

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原子和锁》 的第四章,讲的是如何构建我们自己的自旋锁,看完该章体会到‘思想’的重要性, 它能决定把 工具 使用到那种程度! 你希望你能使用的到那种程度呢?

参考资料

《Rust原子和锁》