聊聊 Rust 共享所有權之 Rc 和 Arc
1v1 所有權
Rust
中所有權約束了值只能有一個所有者,當值離開作用域時,它將被銷燬。
像如下代碼,字符串a
如果直接移動給b
後就沒法後邊再去打印,因爲它的所有權已經轉移給了b
。
let a = String::from("hello");
let b = a;
println!("a = {}, b = {}", a, b);
// got error:
// error[E0382]: borrow of moved value: `a`
// 3 | let a = String::from("hello");
// | - move occurs because `a` has type `String`, which does not implement the `Copy` trait
// 4 | let b = a;
// | - value moved here
// 5 | println!("a = {}, b = {}", a, b);
// | ^ value borrowed here after move
深拷貝(clone)
如果clone
的話可以複製一份,但是這樣的話就需要開闢一塊新的內存,不是很高效。
let a = String::from("hello");
// let b = a;
let b = a.clone();
println!("a = {}, b = {}", a, b);
// output: a = hello, b = hello
引用計數 (reference count)
想要實現多個所有者,又開銷小,可以用引用計數,對應的類型是Rc
。
Rc
只會在複製時增加引用計數,當引用計數爲 0 時,會自動調用drop
方法,釋放內存。
let a = Rc::new(String::from("hello"));
let _b = Rc::clone(&a);
{
let _b = Rc::clone(&a);
println!("reference count {}", Rc::strong_count(&a));
// 3, will be 2 after this block out of scope
}
println!("reference count {}", Rc::strong_count(&a)); // 2
寫時複製(copy on write)
Rc
引用的值是不可變的,如果想要修改,可以使用Rc::make_mut
方法,它會檢查引用計數,在有別的有效引用(strong
)時,會複製一份,然後修改。否則就直接修改原來的值。這也是寫時複製,只有在需要修改時纔會複製。
let mut a = Rc::new(String::from("hello"));
let b = Rc::clone(&a);
// allocate a new string (copy on write)
(*Rc::make_mut(&mut a)).push_str( " world");
println!("{} {}", a, b);
// hello world hello
let c = Rc::clone(&a);
println!("{} {} {}", a, b, c);
// hello world hello hello world
所以這麼用有一個好處,如果有修改,修改是獨立於之前的引用的,不用擔心修改會影響之前引用的值。
當然,如果想保持值修改的同步,可以使用之前提到的
Cell
和RefCell
,這兩個類型可以實現內部可變性,可以在不可變引用的情況下修改值。
循環引用
Rc
是不允許循環引用的,因爲它的引用計數是在編譯時就確定的,如果有循環引用,那麼引用計數永遠不會爲 0,也就永遠不會調用drop
方法,導致內存泄漏。
這裏用官方的一個例子說明:下邊代碼用來描述工具(gadget)和工具所有者(owner)的關係,一個工具可以有一個個所有者,一個所有者可以有多個工具。
如果用Rc
來實現的話,會出現循環引用,工具和工具所有者互相引用,導致誰都無法對引用計數減一,也就無法釋放對應的內存。
use std::{rc::Rc, cell::RefCell};
struct Owner {
name: String,
gadgets: RefCell<Vec<Rc<Gadget>>>
}
struct Gadget {
id: i32,
owner: Rc<Owner>
}
fn main() {
let gadget_owner : Rc<Owner> = Rc::new(
Owner { name: String::from("Gadget Man"), gadgets: RefCell::new(vec![]) }
);
// 兩個工具,都有同一個所有者
let gadget1 = Rc::new(Gadget { id: 1, owner: gadget_owner.clone() });
let gadget2 =Rc::new(Gadget { id: 2, owner: gadget_owner.clone() });
gadget_owner.gadgets.borrow_mut().push(gadget1.clone());
gadget_owner.gadgets.borrow_mut().push(gadget2.clone());
// 釋放gadget_owner的引用計數,保留工具的owner引用計數
drop(gadget_owner);
println!("strong count of gadget1: {}", Rc::strong_count(&gadget1));
// strong count of gadget1: 2
println!("strong count of gadget1.owner: {}", Rc::strong_count(&gadget1.owner));
// strong count of gadget1.owner: 2
// 釋放gadget1的引用計數,正常沒有引用循環的話,owner對應的引用計數也需要釋放
// 但是gadget1的owner的引用計數不會減一,導致內存泄漏
drop(gadget1);
println!("strong count of gadget2.owner: {}", Rc::strong_count(&gadget2.owner));
// strong count of gadget2.owner: 2
}
循環引用如下圖所示
gadgets和owner的引用形成了一個環,誰也沒法釋放,對應的引用計數無法減到0,也就沒法釋放
+-----------+ +-----------+
| Owner |<------| Gadget |
| | | |
| Rc | | Rc |
| | | |
| gadgets --|------>| owner ----+
+-----------+ +-----------+
弱引用
這個時候就是弱引用的用武之地了,弱引用不會增加引用計數,所以不會導致循環引用。
但是它也不能保證引用的值一定存在,因爲它的引用計數可能爲 0,所以用時,需要用upgrade
方法來獲取Option
類型的引用。
也就是說引用的值釋放與否只取決於強引用的引用計數。
use std::rc::Rc;
use std::rc::Weak;
use std::cell::RefCell;
struct Owner {
name: String,
gadgets: RefCell<Vec<Weak<Gadget>>>
}
struct Gadget {
id: i32,
owner: Rc<Owner>
}
fn main() {
let gadget_owner : Rc<Owner> = Rc::new(
Owner {
name: "Gadget Man".to_string(),
gadgets: RefCell::new(Vec::new())
}
);
let gadget1 = Rc::new(Gadget{id: 1, owner: gadget_owner.clone()});
let gadget2 = Rc::new(Gadget{id: 2, owner: gadget_owner.clone()});
gadget_owner.gadgets.borrow_mut().push(Rc::downgrade(&gadget1.clone()));
gadget_owner.gadgets.borrow_mut().push(Rc::downgrade(&gadget2.clone()));
for gadget_opt in gadget_owner.gadgets.borrow().iter() {
let gadget = gadget_opt.upgrade().unwrap();
println!("Gadget {} owned by {}", gadget.id, gadget.owner.name);
}
drop(gadget_owner);
println!("strong count of gadget1: {}", Rc::strong_count(&gadget1));
// strong count of gadget1: 1
println!("strong count of gadget1.owner: {}", Rc::strong_count(&gadget1.owner));
// strong count of gadget1.owner: 2
drop(gadget1);
println!("strong count of gadget2.owner: {}", Rc::strong_count(&gadget2.owner));
// strong count of gadget2.owner: 1
}
線程安全
Rc
是線程不安全的,如果想要在多線程中使用,可以使用Arc
,它是Rc
的線程安全版本。(A
代表atomic
)
use std::sync::Arc;
use std::thread;
fn main() {
let val = Arc::new(5);
for _ in 0..3 {
let val = Arc::clone(&val);
thread::spawn(move || {
let v = *val.as_ref() + 1;
println!("{v:?}");
});
}
thread::sleep(std::time::Duration::from_secs(1));
}
而如果想要在多線程中修改值,可以使用Mutex
和RwLock
,它們都是線程安全的。如Arc<Mutex<T>>
。
最後還有一點想提下,Rc<T>
和Arc<T>
都實現了自動解引用Deref
到T
,所以可以直接在Rc<T>
和Arc<T>
上調用T
的方法。而爲了防止方法名衝突,一般習慣用全限定語法調用方法來調用Rc<T>
和Arc<T>
的方法,如Rc::clone
。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/zr7_9xbWYgYMAOVBda4Dxg