前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >聊聊共享所有权之Rc和Arc

聊聊共享所有权之Rc和Arc

作者头像
newbmiao
发布2023-11-27 12:31:22
2840
发布2023-11-27 12:31:22
举报
文章被收录于专栏:学点Rust

文章目录

  • 1v1 所有权
  • 深拷贝(clone)
  • 引用计数 (reference count)
  • 写时复制(copy on write)
  • 循环引用
  • 弱引用
  • 线程安全

1v1 所有权

Rust中所有权约束了值只能有一个所有者,当值离开作用域时,它将被销毁。

像如下代码,字符串a如果直接移动给b后就没法后边再去打印,因为它的所有权已经转移给了b

代码语言:javascript
复制
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的话可以复制一份,但是这样的话就需要开辟一块新的内存,不是很高效。

代码语言:javascript
复制
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方法,释放内存。

代码语言:javascript
复制
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)时,会复制一份,然后修改。否则就直接修改原来的值。这也是写时复制,只有在需要修改时才会复制。

代码语言:javascript
复制
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

所以这么用有一个好处,如果有修改,修改是独立于之前的引用的,不用担心修改会影响之前引用的值。

当然,如果想保持值修改的同步,可以使用之前提到的CellRefCell,这两个类型可以实现内部可变性,可以在不可变引用的情况下修改值。

循环引用

Rc是不允许循环引用的,因为它的引用计数是在编译时就确定的,如果有循环引用,那么引用计数永远不会为 0,也就永远不会调用drop方法,导致内存泄漏。

这里用官方的一个例子说明:下边代码用来描述工具(gadget)和工具所有者(owner)的关系,一个工具可以有一个个所有者,一个所有者可以有多个工具。

如果用Rc来实现的话,会出现循环引用,工具和工具所有者互相引用,导致谁都无法对引用计数减一,也就无法释放对应的内存。

代码语言:javascript
复制
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
}

循环引用如下图所示

代码语言:javascript
复制
gadgets和owner的引用形成了一个环,谁也没法释放,对应的引用计数无法减到0,也就没法释放

+-----------+       +-----------+
|   Owner   |<------|  Gadget   |
|           |       |           |
|   Rc      |       |   Rc      |
|           |       |           |
| gadgets --|------>| owner ----+
+-----------+       +-----------+

弱引用

这个时候就是弱引用的用武之地了,弱引用不会增加引用计数,所以不会导致循环引用。

但是它也不能保证引用的值一定存在,因为它的引用计数可能为 0,所以用时,需要用upgrade方法来获取Option类型的引用。

也就是说引用的值释放与否只取决于强引用的引用计数。

代码语言:javascript
复制
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

代码语言:javascript
复制
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));
}

而如果想要在多线程中修改值,可以使用MutexRwLock,它们都是线程安全的。如Arc<Mutex<T>>


最后还有一点想提下,Rc<T>Arc<T>都实现了自动解引用DerefT,所以可以直接在Rc<T>Arc<T>上调用T的方法。而为了防止方法名冲突,一般习惯用全限定语法调用方法来调用Rc<T>Arc<T>的方法,如Rc::clone


推荐阅读

如果有用,点个 在看 ,让更多人看到

外链不能跳转,戳 阅读原文 查看参考资料

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-10-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 菜鸟Miao 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1v1 所有权
  • 深拷贝(clone)
  • 引用计数 (reference count)
  • 写时复制(copy on write)
  • 循环引用
  • 弱引用
  • 线程安全
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档