Rust-Rc和Arc


Rust的机制,要求一个资源同一时刻有且只能有一个拥有所有权的绑定或&mut引用,目的为保证内存的安全。在大多数情况下,都没有问题,但是考虑以下情况:

  • 在图数据结构中,多个边可能会拥有同一个节点,该节点直到没有边指向它时,才应该被释放清理。
  • 在多线程中,多个线程可能会持有同一个数据,但是你受限于Rust的安全机制,无法同时获取此数据的可变引用。

为了解决此类问题,Rust在所有权机制之外又引入了额外的措施来简化相应的实现:通过引用计数的方式,允许一个数据资源在同一时刻拥有多个所有者。

这种实现机制就是RcArc,前者适用于单线程,后者适用于多线程。

Rc

引用计数(reference counting),顾名思义,通过记录一个数据被引用的次数来确定该数据是否正在被使用。当引用次数归零时,就代表该数据不再被使用,因此可以被清理释放。

而Rc正是引用计数的英文缩写。当我们希望在堆上分配一个对象供程序的多个部分使用且无法确定哪个部分最后一个结束时,就可以使用Rc成为数据据值的所有者

以下是经典的所有权被转移导致报错的例子:

    let s = String::from("hello");
    //s在这里转移给a
    let a = Box::new(s);
    //报错。这里继续尝试把s转移给b
    let b = Box::new(s);
error[E0382]: use of moved value: `s`
  --> src/main.rs:33:22
   |
29 |     let s = String::from("hello");
   |         - move occurs because `s` has type `String`, which does not implement the `Copy` trait
30 |     //s在这里转移给a
31 |     let a = Box::new(s);
   |                      - value moved here
32 |     //报错。这里继续尝试把s转移给b
33 |     let b = Box::new(s);
   |                      ^ value used here after move

使用Rc就可以解决:

    let s = Rc::new(String::from("hello"));
    let b = Rc::clone(&s);

以上代码我们使用Rc::new创建了一个新的Rc智能指针并赋给变量s,该指针指向底层的字符串数据。

智能指针Rc在创建时,还会将此用计数加1,此时获取引用计数的关联函数Rc::strong_count返回的值将是1。

Rc::clone

接着,我们又使用Rc::clone克隆了一份智能指针Rc,并将该智能指针的引用计数增加到2。

不要被clone所迷惑,以为所有的clone都是深拷贝。这里的clone仅仅复制了智能指针并增加了引用计数,并没有克隆底层数,因为a和b是共享了底层的字符串s,这种复制效率是非常高的。当然我们也可以使用s.clone()的方式来克隆,但是从可读性角度,我们更加推荐Rc::clone的方式。

Arc

Arc是 Atomic Rc 的缩写,顾名思义:原子化的Rc智能指针。原子化是一种并发原语,我们在后续章节会进行深入讲解,这里你只要知道它能保证我们的数据能够安全的在线程间共享即可。

Arc 的性能损耗

你可能好奇,为何不直接使用Arc,还要画蛇添足弄一个Rc,还有 Rust 的基本数据类型、标准库数据类型为什么不自动实现原子化操作?这样就不存在线程不安全的问题了。

原因在于原子化或者其它锁虽然可以带来的线程安全,但是都会伴随着性能损耗,而且这种性能损耗还不小。因此 Rust 把这种选择权交给你,毕竟需要线程安全的代码其实占比并不高,大部分时候我们开发的程序都在一个线程内。

学习内容传送门