rust 语言并没有提供垃圾回收(GC, Garbage Collection )的功能, 不过它提供了最简单的引用计数包装类型 Rc,这种引用计数功能也是早期 GC 常用的方法, 但是引用计数不能解决循环引用,所以 rust 同时还提供了 Weak 类型用来避免循环引用。

rust 语言并没有提供垃圾回收(GC, Garbage Collection )的功能, 不过它提供了最简单的引用计数包装类型 Rc,这种引用计数功能也是早期 GC 常用的方法, 但是引用计数不能解决循环引用,所以 rust 同时还提供了 Weak 类型用来避免循环引用。

Rc、Weak 示例

首先看一下 Rc 的一个例子:

  1. use std::rc::Rc;
  2. fn main() {
  3. let a = Rc::new(1);
  4. println!("a's reference count is {}", Rc::strong_count(&a));
  5. let b = Rc::clone(&a);
  6. println!("a's reference count is {} after clone b", Rc::strong_count(&a));
  7. let c = Rc::clone(&a);
  8. println!("a's reference count is {} after clone c", Rc::strong_count(&a));
  9. println!("a = {}, b = {}, c = {}", a, b, c);
  10. println!("b's reference count is {}", Rc::strong_count(&b));
  11. println!("c's reference count is {}", Rc::strong_count(&c));
  12. drop(a);
  13. println!("c's reference count is {} after drop `a`", Rc::strong_count(&c));
  14. drop(b);
  15. println!("c's reference count is {} after drop `b`", Rc::strong_count(&c));
  16. }

运行结果为:

  1. a's reference count is 1
  2. a's reference count is 2 after clone b
  3. a's reference count is 3 after clone c
  4. a = 1, b = 1, c = 1
  5. b's reference count is 3
  6. c's reference count is 3
  7. c's reference count is 2 after drop `a`
  8. c's reference count is 1 after drop `b`

从这里可以看出,abc 三个变量同时指向了同一个对象, 这个对象的引用计数会随着 clone 调用而加 1,随着 drop 调用而减 1, 当最后一个变量被 drop 时,引用计数会变为 0,进而触发释放对象被占用的堆内存。
如果 Rc 包装的对象是一个容器类型时,有可能会产生循环引用,比如像下面这样的类型:

  1. struct Car {
  2. name: String,
  3. whells: RefCell<Vec<Rc<Wheel>>>,
  4. }
  5. struct Wheel {
  6. id: i32,
  7. car: Rc<Car>,
  8. }

就有可能会出现:Car -> Wheel -> Car 类型的循环引用, 这个循环应用所涉及的对象的引用计数永远都不可能为0, 所占用的内存永远也不会得到释放, 直到进程结束,这样就造成了内存泄露,为了避免这种情况,rust 还提供了 Weak 类型, 和 Rc 类型来协同使用:

  1. use std::cell::RefCell;
  2. use std::rc::{Rc, Weak};
  3. struct Car {
  4. name: String,
  5. whells: RefCell<Vec<Weak<Wheel>>>,
  6. }
  7. struct Wheel {
  8. id: i32,
  9. car: Rc<Car>,
  10. }
  11. fn main() {
  12. let car: Rc<Car> = Rc::new(
  13. Car {
  14. name: "A".to_string(),
  15. whells: RefCell::new(vec![]),
  16. }
  17. );
  18. let whell1 = Rc::new(
  19. Wheel {
  20. id: 1,
  21. car: Rc::clone(&car),
  22. }
  23. );
  24. let whell2 = Rc::new(
  25. Wheel {
  26. id: 2,
  27. car: Rc::clone(&car),
  28. }
  29. );
  30. let mut whells = car.whells.borrow_mut();
  31. whells.push(Rc::downgrade(&whell1));
  32. whells.push(Rc::downgrade(&whell2));
  33. drop(whells);
  34. for whell_weak in car.whells.borrow().iter() {
  35. let whell = whell_weak.upgrade().unwrap();
  36. println!("Whell {} owned by {}", whell.id, whell.car.name);
  37. }
  38. }

Rc、Weak 原理

首先我们从源码看看 RcWeak 的结构:

  1. struct RcBox<T: ?Sized> {
  2. strong: Cell<usize>,
  3. weak: Cell<usize>,
  4. value: T,
  5. }
  6. struct Rc<T: ?Sized> {
  7. ptr: Shared<RcBox<T>>,
  8. }
  9. struct Weak<T: ?Sized> {
  10. ptr: Shared<RcBox<T>>,
  11. }

从上面可以看出:RcWeak 内存表示并没有什么不同,内部都是存放一个指向 RcBoX 类型的指针, 这个 RcBoX 类型的指针指向堆的某个地方。
RcWeak 的真正不同的地方是针对指向的 RcBox 内部的 strongweak 的处理上面, 其中 strong 属性用来表示 value 对象的强引用次数(strong reference count), weak 属性用来表示 value 对象的弱引用次数(weak reference count,其实是弱引用次数加 1), 对它们的操作有如下规则:

  • 当初始化一个 Rc 时,RcBox.strongRcBox.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)

这样就可以通过 RcWeak 像上面协同使用就避免了循环引用了。

Rc、Weak 限制

由于 RcWeak 并没有实现 SendSync trait, 所以这两种包装类型只能用于单线程中,不能跨线程操作, 如果需要跨线程操作,就需要用到 std::sync::Arcstd::sync::Weak 了。

std::sync::Arc 和 std::sync::Weak 原理

先看一下它们的源码:

  1. struct ArcInner<T: ?Sized> {
  2. strong: atomic::AtomicUsize,
  3. weak: atomic::AtomicUsize,
  4. data: T,
  5. }
  6. struct Arc<T: ?Sized> {
  7. ptr: Shared<ArcInner<T>>,
  8. }
  9. struct Weak<T: ?Sized> {
  10. ptr: Shared<ArcInner<T>>,
  11. }

它们与 std::rc::Rcstd::rc::Weak 类同, 只是 strongweak 的类型使用了线程安全的 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