Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

RwLock<T>

本集目標

學會用 RwLock<T> 實現讀寫分離的鎖,以及和 Mutex 的比較。

概念說明

Mutex 的限制

Mutex 不管你要讀還是要寫,都要鎖住。但很多時候多個執行緒只是要讀資料——讀和讀之間不會衝突,全部鎖住太浪費了。

RwLock:讀寫分離

RwLock<T> 區分讀鎖和寫鎖:

  • 讀鎖.read().expect(...)):多個執行緒可以同時持有讀鎖。
  • 寫鎖.write().expect(...)):寫鎖是獨佔的,持有寫鎖時不能有任何讀鎖或其他寫鎖。

「多個讀者同時」這件事一條執行緒示範不出來,本集最後的範例程式碼會用三條執行緒跑給你看。這裡先看兩種鎖怎麼取得:

use std::sync::RwLock;

fn main() {
    let lock = RwLock::new(42);

    // 讀鎖:只能看
    {
        let r = lock.read().expect("讀鎖失敗");
        println!("讀到 {}", *r);
    } // r 在這裡 drop,讀鎖放掉

    // 寫鎖:獨佔,可以改
    {
        let mut w = lock.write().expect("寫鎖失敗");
        *w += 1;
    } // w 在這裡 drop,寫鎖放掉

    println!("現在是 {}", *lock.read().expect("讀鎖失敗"));
}

guard 的行為

讀鎖回傳 RwLockReadGuard,寫鎖回傳 RwLockWriteGuard。跟 MutexGuard 一樣,它們也是智慧指標——可以直接操作內容,drop 時自動放鎖。

一樣要注意 guard 不要活太久。

RefCell 的對照

RefCellRwLock
執行緒單執行緒多執行緒
規則多個 borrow() 或一個 borrow_mut()多條執行緒的 read() 或一個 write()
檢查方式執行時期,違反會 panic作業系統的鎖,違反會阻塞等待

不過有一個 RefCell 沒有的陷阱:RefCell 可以在同一條執行緒裡 borrow() 好幾次,但 RwLock 說的「多個讀者」指的是多條執行緒。同一條執行緒重複拿同一把 RwLock 的讀鎖,標準庫明說可能會 panic;在某些平台上還可能直接卡住。

Mutex vs RwLock

什麼時候用哪個?

  • Mutex:簡單、開銷小。適合讀寫都頻繁,或鎖持有時間很短的場景。大部分情況下 Mutex 就夠了。
  • RwLock:在讀遠多於寫的時候有優勢,因為多個讀者可以同時進行。但鎖本身的開銷比 Mutex 大,而且有寫者飢餓(writer starvation)的風險——如果讀者一直源源不斷,寫者可能永遠拿不到鎖。

範例程式碼

use std::sync::{Arc, RwLock};
use std::thread;

fn main() {
    let data = Arc::new(RwLock::new(vec![1, 2, 3]));

    let mut handles = vec![];

    // 啟動 3 個讀者
    for i in 0..3 {
        let data = Arc::clone(&data);
        let handle = thread::spawn(move || {
            let read_guard = data.read().expect("讀鎖失敗");
            println!("讀者 {}:{:?}", i, *read_guard);
            // 多個讀者可以同時持有讀鎖
        });
        handles.push(handle);
    }

    // 啟動 1 個寫者
    {
        let data = Arc::clone(&data);
        let handle = thread::spawn(move || {
            let mut write_guard = data.write().expect("寫鎖失敗");
            write_guard.push(4);
            println!("寫者:寫入完成,現在是 {:?}", *write_guard);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().expect("執行緒發生錯誤");
    }

    println!("最終結果:{:?}", *data.read().expect("讀鎖失敗"));
}

重點整理

  • RwLock<T> 區分讀鎖和寫鎖:多條執行緒可以同時讀,寫者獨佔。
  • .read().expect(...) 取得讀鎖,.write().expect(...) 取得寫鎖。
  • guard 透過 Deref 操作內容,drop 時自動放鎖。
  • RefCell 的對照:RefCell 是單執行緒版本,RwLock 是多執行緒版本。
  • 同一條執行緒不要重複拿同一把 RwLock 的讀鎖——那不是 RefCellborrow(),可能 panic 或卡死。
  • Mutex 簡單、開銷小,大部分情況夠用;RwLock 適合讀遠多於寫的場景,但開銷較大且有寫者飢餓的風險。