Rust 中的無鎖編程技術(二)

雖然這個系列博客的標題叫《Rust 中的無鎖編程技術》,但是我們要先了解一個鎖的工作原理,才能夠很好的理解無鎖編程。在上一篇的博客 Rust 中的無鎖編程技術中,我提到了 “鎖” 就是一個協議,協議僅僅只是給出了各種 “鎖” 的行爲規範,也就是對於一個 “鎖” 必須有的外在功能是什麼,但是我並沒有提及 “鎖” 的另一個很重要的方面,就是和數據的綁定

鎖與數據

“鎖”應該和其所保護的數據緊緊綁定在一起的。但是我們之前對 “鎖” 的定義是一種協議,而在編程語言中,協議是不和數據相關的,所以細心的讀者應該能發現在上一篇博客的代碼中:

pub trait RawLock: Default + Send + Sync {
    type Token: Clone;
    fn lock(&self) -> Self::Token;
    /// # Safety
    ///
    /// `unlock()` should be called with the token given by the corresponding `lock()`.
    unsafe fn unlock(&self, token: Self::Token);
}

協議的名稱實際上是 Rawlock,也可以稱之爲 “原始鎖”。確實在上一篇博客中我一直在把 Rawlock 說成爲中文裏面的“鎖” 並不能說是一個很準確的翻譯,並不是有意誤導大家,而是確實一般我們口中常說的 “鎖” 其實就是 “原始鎖”,是對“鎖” 的行爲的描述,但是隻有協議是不夠的,正如標題所說,“鎖”要緊緊的和其保護的數據綁定在一起:

#[repr(C)]
pub struct Lock<L: RawLock, T> {
    lock: L,
    data: UnsafeCell<T>,
}
unsafe impl<L: RawLock, T: Send> Send for Lock<L, T> {}
unsafe impl<L: RawLock, T: Send> Sync for Lock<L, T> {}

嚴格意義上,這個纔是 “鎖” 的真面目。既有對應的行爲規範,也就是要提供 token,lock 函數以及 unlock 函數的實現,也有對數據緊緊的綁定。

值得注意的地方是 UnsafeCell,UnsafeCell 是最基本的 interior mutability 的類型了。RefCell 和 Cell 都是建立於 UnsafeCell 的。而 UnsafeCell 是! Sync 並且 Send 的,但是 “鎖” 當然是應該可以 Sync 的,因此我們人爲的給 Lock 加上 Sync 和 Send。

guard 與數據

在上一篇博客中,我提到了 token 是一個證明,但是和 “鎖” 一樣,光有 token 這個證明是毫無意義的,這個 token 也應該緊緊的和數據綁定在一起,從編程語言的語義來說就是:持有這個 token 的線程可以對鎖裏面的數據進行操作。我們一起看看 guard 是怎麼對 token 進行封裝的。

pub struct LockGuard<'s, L: RawLock, T> {
    lock: &'s Lock<L, T>,
    token: L::Token,
    _marker: PhantomData<*const ()>, // !Send + !Sync
}
unsafe impl<'s, L: RawLock, T: Send> Send for LockGuard<'s, L, T> {}
unsafe impl<'s, L: RawLock, T: Sync> Sync for LockGuard<'s, L, T> {}

這裏非常有意思的是關於 Rust 的 lifetimes 的使用。參照我們上面關於 Lock 的實現,guard 實際上就是對證明以及數據的綁定。而 lifetimes 在這裏的語義是:guard 的生命週期必須小於或等於 lock 的生命週期。Rust 通過 lifetimes 非常好的表達出了 guard 和 lock 的關係。

也就是說,一個線程在持有 guard 的時候,這個線程可以對其 lock 裏面的數據進行操作。所以我們的 “鎖” 應該暴露出來一個 lock 函數,允許當前線程去試着獲取 guard,而在 guard 被 drop 的同時也意味着當前的共享資源又可以被下一個線程訪問了,所以我們需要在 drop 函數做一個 unlock 的動作。

PhantomData 就不在這裏展開說了,這個和 drop check 有關,感興趣的小夥伴自己去看看。

impl<L: RawLock, T> Lock<L, T> {
...
    pub fn lock(&self) -> LockGuard<L, T> {
        let token = self.lock.lock();
        LockGuard {
            lock: self,
            token,
            _marker: PhantomData,
        }
    }
...
}
impl<'s, L: RawLock, T> Drop for LockGuard<'s, L, T> {
    fn drop(&mut self) {
        unsafe { self.lock.lock.unlock(self.token.clone()) };
    }
}

對於持有 guard 的線程可以通過 deref 或 deref_mut 的方式來對數據進行操作:

impl<'s, L: RawLock, T> Deref for LockGuard<'s, L, T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        unsafe { &*self.lock.data.get() }
    }
}
impl<'s, L: RawLock, T> DerefMut for LockGuard<'s, L, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        unsafe { &mut *self.lock.data.get() }
    }
}

最後缺失的一步

我們現在對 “鎖” 有一個更加全面的瞭解了。從一開始我們對 “鎖” 的認識只是一個協議,到現在瞭解到 “鎖” 還需要對其保護的數據有一個綁定關係,我們還了解到所謂的 guard 的本質是證明以及數據的綁定。我們下一個博客就和大家說說最後缺失的一步,究竟如何實現 RawLock 協議。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/1f9OpXKnIEWYeVdwMvsAzg