Rust 中的無鎖編程技術

當多線程同時對共享資源讀寫的時候,我們需要用鎖去保護這個共享資源。

這裏有幾個概念,一條線程指的是進程中一個單一順序的控制流,一個進程中可以並行多個線程,每條線程並行執行不同的任務 [1],共享資源就是“堆(heap)” 中的一段數據,那麼們平常說的 “鎖” 究竟是什麼呢?而我們平常聽到的無鎖編程究竟是什麼呢?在 Rust 開源社區中,crossbeam 這個庫究竟給開發者們提供了什麼工具,解決了什麼問題呢?這些問題我們會慢慢在以後的文章中給大家解答。

我們要解決的核心問題

多線程帶來的最大問題在於其不確定性。在並行併發計算中,我們是沒有辦法控制 cpu 什麼時候執行哪條線程的,我們爲了最大限度地享受 cpu 所給我們帶來的性能優化,只需要將所有的工作交給 cpu,讓 cpu 自己決定什麼時候執行什麼指令。不僅僅是 cpu,實際上,編譯器,操作系統都會有自己的優化策略,都會給我們的程序運行帶來不確定性。所以我們在寫多線程程序的時候,腦海裏面時時刻刻要記住我們最大的三個敵人:

  1. cpu

  2. 編譯器

  3. 操作系統

cpu 和編譯器都會根據自己的優化策略去更改指令的執行順序,而操作系統是負責線程調度的,什麼時候哪條線程獲得了 cpu 的使用權,這是我們無法控制的。

核心問題帶來的另一個問題

上面我們只提到了多線程編程指令執行順序上的不確定性,而忽略了另一個很重要的環節,那就是共享資源的內存管理問題。正是因爲線程執行順序上的不確定性,這使得對於共享內存的管理變得格外複雜。比如在處理無鎖數據結構的時候,從一個線程共享的鏈表裏面刪除一個節點,我們是否能立馬釋放其內存?答案是否定的,因爲我們不確定是否有別的線程還持有對這塊內存的引用。特別是對於 Rust 或者是 C 或者是 C++ 這樣的系統語言,這些語言沒有垃圾回收機制,要求編程者對於共享內存進行手動管理,至於如何管理,這也是我們以後會討論的問題。

鎖應該是最簡單也是最直接可以解決上述兩個痛點的解決方案了。鎖給我們對多線程的運行提供了一個非常好的 “模型”。這個模型是這樣的:

那麼這個神奇的 “鎖” 究竟是什麼呢?在 Rust 中又是如何實現的呢?

鎖其實是一個協議(protocal),在 Rust 裏面我們稱之爲 trait。我們一起來看看這個 trait:

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);
}

這裏最主要要看到這幾個概念 Token lock 函數以及 unlock 函數的簽名。我們今天主要講講這個 token 是什麼。

token 我們可以將其理解爲一個證明,在編程語言的語義中,這個證明是證明當前持有這個 token 的線程可以對這個共享資源進行任何操作。而其他沒有這個證明的線程,只能卡在搶鎖的代碼處。

在 Rust 的 std 裏面,lock 函數返回的是一個 LockResult<MutexGuard<'_, T>>,暫時不用管 LockResult,MutextGuard 實際上就是對我們 token 的進一步的封裝。

那麼我們從以上的定義可以得到以下幾個信息:

  1. 任何的鎖,包括 spinlock,ticketlock,mcslock,等等,都必須實現以上的協議,也就是,必須提供 token,提供 lock 函數的實現,提供 unlock 函數的實現。

  2. 任何的鎖,都應該是 Send 以及 Sync 的,也就是說,任何的鎖都可以安全的在線程中轉移 (Send),並且其借用(&)也必須是可以安全的在線程中轉移 (Sync)。

  3. lock 函數成功返回的話,代表當前線程獲得了一個可以訪問共享資源的證明。

  4. unlock 函數的簽名中,需要提供 token 的 ownership,並且這個函數是 unsafe 的,雖然函數本身並沒有需要 deref raw pointer 這樣的操作,但語言本身是無法保證調用這個函數的線程傳入的 token 是否就是這個線程在之前調用 lock 成功時所返回的 token。所以我們加上 unsafe 來提醒使用者。

我們現在對 “鎖” 有一個初步的認識了,但是我們還沒有提及另一個非常重要的概念,就是 guard,也就是上面提及的對 token 的進一步的封裝,在下一篇文章中,我會再慢慢和大家解釋這個 “證明” 究竟是什麼。

[1] wiki: thread

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