原文標題: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
類型的全部引用都結束以前,它不會被丟棄——所以即便在mai
n函數結束以後,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
當foo
或bar
離開做用域時,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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。