Arc in Rust

語言: CN / TW / HK

前言

在Rust語言中,所有權大多數情況下是明確的,對於一個給定的值,你可以準確地判斷出,哪個變數擁有它。但是也存在一些情況,單個值可能同時被多個所有者持有。

請看下面的例子:

  fn process_files_in_parallel(filenames: Vec<string>,  glossary: &GigabyteMap) 
     -> io::Result<()> 
 {
      ...
      for worklist in worklists
      {
          thread_handlers.push(
              spawn(move || process_files(worklist, glossary)
          );
      }
      ....
 }
`

上面的例子中,擬採用多個程序,併發地處理檔案。但是處理檔案需要一個消耗巨大記憶體的資料結構, glossary,可能資料結構大小超過GB,這種情況下,拷貝多個副本給不同執行緒使用無疑是無法承受的。但是上面的程式碼,存在一個問題,即glossary的生命週期。

因為glossary並不能保證在所有的子程序完成任務之前不被銷燬,如果被銷燬,會導致子程序無法正常完成任務。

Rc and Arc

Rust提供了一個名為Rc 的型別來支援多重所有權,它名稱中的Rc是Reference Counting 引用計數的縮寫。Rc 型別會在內部維護一個記錄值引用計數的計數器,從而確定這個值是否仍在被使用。如果對一個值的引用計數變成了0,意味著這個值可以被安全地清理掉。

那Arc是幹啥的呢?Arc具備Rc的一切特徵,那是Rc不是執行緒安全的,所以Rust又提供了一個Arc 的型別,來確保多個執行緒之前操作某個變數的安全性。這個Arc中的A指的是Atomic,即Atomic Reference Counting。

那看起來是Arc是更強大的Rc,能夠保證安全地用在併發的場景,那為什麼不乾脆消滅掉Rc,統統只使用Arc呢?

沒有免費的午餐,Arc更強大,但是這種強大,需要付出一些效能開銷才能做到,如果是單執行緒條件下,我們其實不需要付出這種開銷。因此

  • 單執行緒條件下,可以使用Rc
  • 多執行緒併發條件下,建議使用Arc
use std::thread;
use std::time::Duration;
use std::sync::Arc;


fn main() {
    let foo = Arc::new(vec![0]);
    for _ in 0..10 {
        let bar = Arc::clone(&foo);
        thread::spawn(move || {
            thread::sleep(Duration::from_millis(200));
            println!("{:?}", &bar);
        });
    }
    println!("{:?}", foo);
}

上面的用法中, Arc::clone 並不是複製了一份vec,而是僅僅增減了一個引用計數,這是多個執行緒之間共享資料的一個方法。

回到我們最開始的問題,巨大的資料結構,多個執行緒之間共享:

fn process_files_in_parallel(filenames: Vec<String>, glossary: Arc<GigabyteMap>)
   -> io::Result<()>
{
   ....
   for worklist in worklists 
   {
       let glossary_for_child = glossary.clone() ;
       thread_handlers.push(
           spawn(move || process_files(worklist, &glossary_for_child))
       );
   }
   ....
   
}