rust 语言并没有提供垃圾回收(GC, Garbage Collection )的功能, 不过它提供了最简单的引用计数包装类型 Rc,这种引用计数功能也是早期 GC 常用的方法, 但是引用计数不能解决循环引用,所以 rust 同时还提供了 Weak 类型用来避免循环引用。
rust 语言并没有提供垃圾回收(GC, Garbage Collection )的功能, 不过它提供了最简单的引用计数包装类型 Rc,这种引用计数功能也是早期 GC 常用的方法, 但是引用计数不能解决循环引用,所以 rust 同时还提供了 Weak 类型用来避免循环引用。
Rc、Weak 示例
首先看一下 Rc 的一个例子:
use std::rc::Rc;fn main() {let a = Rc::new(1);println!("a's reference count is {}", Rc::strong_count(&a));let b = Rc::clone(&a);println!("a's reference count is {} after clone b", Rc::strong_count(&a));let c = Rc::clone(&a);println!("a's reference count is {} after clone c", Rc::strong_count(&a));println!("a = {}, b = {}, c = {}", a, b, c);println!("b's reference count is {}", Rc::strong_count(&b));println!("c's reference count is {}", Rc::strong_count(&c));drop(a);println!("c's reference count is {} after drop `a`", Rc::strong_count(&c));drop(b);println!("c's reference count is {} after drop `b`", Rc::strong_count(&c));}
运行结果为:
a's reference count is 1a's reference count is 2 after clone ba's reference count is 3 after clone ca = 1, b = 1, c = 1b's reference count is 3c's reference count is 3c's reference count is 2 after drop `a`c's reference count is 1 after drop `b`
从这里可以看出,a、b、c 三个变量同时指向了同一个对象, 这个对象的引用计数会随着 clone 调用而加 1,随着 drop 调用而减 1, 当最后一个变量被 drop 时,引用计数会变为 0,进而触发释放对象被占用的堆内存。
如果 Rc 包装的对象是一个容器类型时,有可能会产生循环引用,比如像下面这样的类型:
struct Car {name: String,whells: RefCell<Vec<Rc<Wheel>>>,}struct Wheel {id: i32,car: Rc<Car>,}
就有可能会出现:Car -> Wheel -> Car 类型的循环引用, 这个循环应用所涉及的对象的引用计数永远都不可能为0, 所占用的内存永远也不会得到释放, 直到进程结束,这样就造成了内存泄露,为了避免这种情况,rust 还提供了 Weak 类型, 和 Rc 类型来协同使用:
use std::cell::RefCell;use std::rc::{Rc, Weak};struct Car {name: String,whells: RefCell<Vec<Weak<Wheel>>>,}struct Wheel {id: i32,car: Rc<Car>,}fn main() {let car: Rc<Car> = Rc::new(Car {name: "A".to_string(),whells: RefCell::new(vec![]),});let whell1 = Rc::new(Wheel {id: 1,car: Rc::clone(&car),});let whell2 = Rc::new(Wheel {id: 2,car: Rc::clone(&car),});let mut whells = car.whells.borrow_mut();whells.push(Rc::downgrade(&whell1));whells.push(Rc::downgrade(&whell2));drop(whells);for whell_weak in car.whells.borrow().iter() {let whell = whell_weak.upgrade().unwrap();println!("Whell {} owned by {}", whell.id, whell.car.name);}}
Rc、Weak 原理
首先我们从源码看看 Rc 和 Weak 的结构:
struct RcBox<T: ?Sized> {strong: Cell<usize>,weak: Cell<usize>,value: T,}struct Rc<T: ?Sized> {ptr: Shared<RcBox<T>>,}struct Weak<T: ?Sized> {ptr: Shared<RcBox<T>>,}
从上面可以看出:Rc 和 Weak 内存表示并没有什么不同,内部都是存放一个指向 RcBoX 类型的指针, 这个 RcBoX 类型的指针指向堆的某个地方。Rc 和 Weak 的真正不同的地方是针对指向的 RcBox 内部的 strong、weak 的处理上面, 其中 strong 属性用来表示 value 对象的强引用次数(strong reference count), weak 属性用来表示 value 对象的弱引用次数(weak reference count,其实是弱引用次数加 1), 对它们的操作有如下规则:
- 当初始化一个
Rc时,RcBox.strong和RcBox.weak都被初始化为 1 - 当执行
Rc::clone时,RcBox.strong加 1, 当drop一个Rc时,RcBox.strong减 1, 如果此时RcBox.strong变为 0,就再将RcBox.weak减 1, 如果此时RcBox.weak等于 0,就释放RcBox类型对象所占用的内存。 - 当执行
Rc.downgrade时,返回一个Weak引用,并将RcBox.weak加 1, 当drop一个Weak时,RcBox.weak减 1,当RcBox.weak等于 0 时,就释放RcBox类型对象所占用的内存。 - 当执行
Weak.upgrade时,如果此时RcBox.strong为 0 返回None, 否则RcBox.strong加 1,返回Some(Rc)。
这样就可以通过 Rc 和 Weak 像上面协同使用就避免了循环引用了。
Rc、Weak 限制
由于 Rc 和 Weak 并没有实现 Send 和 Sync trait, 所以这两种包装类型只能用于单线程中,不能跨线程操作, 如果需要跨线程操作,就需要用到 std::sync::Arc 和 std::sync::Weak 了。
std::sync::Arc 和 std::sync::Weak 原理
先看一下它们的源码:
struct ArcInner<T: ?Sized> {strong: atomic::AtomicUsize,weak: atomic::AtomicUsize,data: T,}struct Arc<T: ?Sized> {ptr: Shared<ArcInner<T>>,}struct Weak<T: ?Sized> {ptr: Shared<ArcInner<T>>,}
它们与 std::rc::Rc 和 std::rc::Weak 类同, 只是 strong 和 weak 的类型使用了线程安全的 atomic 类型, 当然这也带来了一部分性能损失。
转自:https://fugangqiang.github.io/posts/rust/rust%E4%B9%8BRc%E3%80%81Weak.html
rust 程序设计:https://kaisery.github.io/trpl-zh-cn/ch15-06-reference-cycles.html
