【譯】Arc 在 Rust 中是如何工做的


原文標題:How Arc works in Rust
原文連接:https://medium.com/@DylanKerler1/how-arc-works-in-rust-b06192acd0a6
公衆號:Rust 碎碎念
翻譯 by:Prayingweb

原子引用計數(Arc)類型是一種智能指針,它可以讓你以線程安全的方式在線程間共享不可變數據。我尚未發現可以很好地解釋它的工做原理的文章,因此我決定嘗試來寫一篇。(文章)第一部分是介紹怎樣使用Arc和爲何要使用Arc;若是你已經瞭解這部份內容,只是想知道它是如何工做的,能夠直接跳到第二部分:「它是怎樣工做的(How does it work)」。安全

爲何你須要使用Arc?

當你試圖在線程間共享數據時,須要Arc類型來保證被共享的類型的生命週期,與運行時間最長的線程活得同樣久。考慮下面的例子:微信

use std::thread;
use std::time::Duration;

fn main() {
  let foo = vec![0]; // creation of foo here
  thread::spawn(|| {
    thread::sleep(Duration::from_millis(20));
    println!("{:?}", &foo); 
  });
// foo gets dropped here

// wait 20 milliseconds
// try to print foo

這段代碼沒法編譯經過。咱們會獲得一個錯誤,稱foo的引用活得比foo自身更久。這是由於foo在main函數結尾處就被丟棄(drop)了,而且這個被丟棄的值會在20毫秒後在生成的線程中被試圖訪問。這就是Arc的做用所在。原子引用計數確保在對foo類型的全部引用都結束以前,它不會被丟棄——所以即便在main函數結束以後,foo仍然會存在。如今考慮下面的示例:編輯器

use std::thread;
use std::sync::Arc;
use std::time::Duration;

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

在這個例子中,咱們能夠在(主)線程中引用foo而且還能夠在(子)線程被生成以後訪問它的值。函數

它是怎樣工做的?

你已經知道如何使用Arc了,如今讓咱們討論一下它是如何工做的。當你調用let foo = Arc::new(vec![0])時,你同時建立了一個vec![0]和一個值爲1的原子引用計數,而且把它們都存儲在堆上的相同位置(緊挨着)。指向堆上的這份數據的指針存放在foo中。所以,foo是由指向一個對象的指針構成,被指向的對象包含vec![0]和原子計數。
atom

當你調用let bar = Arc::clone(&foo)時,你是在獲取foo的一個引用、對foo(指向存放在堆上的數據的指針)解引用、接着找到foo指向的地址、找出裏面存放的值(vec![0]和原子計數)、把原子計數加1、最後把指向vec![0]的指針保存在bar中。
url

foobar離開做用域時,Arc::drop()就被調用了,原子計數減一。若是Arc::drop()發現原子計數等於0,那麼它所指向的堆上的數據(vec![0]和原子計數)會被清理並從堆上擦除。
spa

原子計數是一種可以讓你以線程安全的方式修改和增長它的值的類型;在對原子類型容許進行其餘操做以前,前面的原子類型操做必需要所有完成;所以被稱爲原子的(atomic)(即不可分割的)(操做)。
.net

須要注意的是,Arc只能包含不可變數據。這是由於若是兩個線程試圖在同一時間修改被包含的值,Arc沒法保證避免數據競爭。若是你但願修改數據,你應該在Arc類型內部封裝一個互斥鎖保護(Mutex guard)。線程

爲何這些東西能讓Arc是線程安全的呢?

Arc是線程安全的是由於它給編譯器保證數據的引用至少活得和數據自己同樣長(譯註:這裏原做者應該是想表達,數據的引用存在期間,數據都是有效的)。這是由於每次你建立一個對堆上數據得引用,原子計數就會加一,數據只有在當原子計數等於零得時候纔會被丟棄(每當一個引用離開做用域時,原子計數會減一)——Arc和一個普通得Rc(引用計數)之間得區別就在於原子計數。

那麼Rc有什麼用,爲何不用Arc來作全部事情?

緣由是,原子計數是一個(開銷)昂貴的變量類型,而普通的usize類型則沒有這些開銷。原子類型不只在實際的程序中佔用更多的內存,並且每一個原子類型的操做還須要更長的時間,由於它必須分配資源來爲對其自身進行讀寫的調用維護一個隊列進而保證原子性。

本文分享自微信公衆號 - Rust語言中文社區(rust-china)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索