原文标题:How Arc works in Rust


原文链接:https://medium.com/@DylanKerler1/how-arc-works-in-rust-b06192acd0a6


公众号: Rust 碎碎念


翻译 by: Praying

原子引用计数(Arc)类型是一种智能指针,它能够让你以线程安全的方式在线程间共享不可变数据。我还没有发现能够很好地解释它的工作原理的文章,所以我决定尝试来写一篇。(文章)第一部分是介绍怎样使用Arc和为什么要使用Arc;如果你已经了解这部分内容,只是想知道它是如何工作的,可以直接跳到第二部分:“它是怎样工作的(How does it work)”。

为什么你需要使用Arc?

当你试图在线程间共享数据时,需要Arc类型来保证被共享的类型的生命周期,与运行时间最长的线程活得一样久。考虑下面的例子:

use std::thread;
use std::time::Duration;

fn main() {
  let foo = vec![0]; // creation of foo here
  thread::spawn(|| {
    thread::sleep(Duration::from_millis(20));
    println!("{:?}", &foo); 
  });
} // foo gets dropped here

// wait 20 milliseconds
// try to print foo

这段代码无法编译通过。我们会得到一个错误,称foo的引用活得比foo自身更久。这是因为foo在main函数结尾处就被丢弃(drop)了,并且这个被丢弃的值会在20毫秒后在生成的线程中被试图访问。这就是Arc的作用所在。原子引用计数确保在对foo类型的所有引用都结束之前,它不会被丢弃——因此即使在main函数结束之后,foo仍然会存在。现在考虑下面的示例:

use std::thread;
use std::sync::Arc;
use std::time::Duration;

fn main() {
  let foo = Arc::new(vec![0]); 
  let bar = Arc::clone(&foo);
  thread::spawn(move || {
    thread::sleep(Duration::from_millis(20));
    println!("{:?}", *bar);
  });
  
  println!("{:?}", foo);

在这个例子中,我们可以在(主)线程中引用foo并且还可以在(子)线程被生成之后访问它的值。

它是怎样工作的?

你已经知道如何使用Arc了,现在让我们讨论一下它是如何工作的。当你调用let foo = Arc::new(vec![0])时,你同时创建了一个vec![0]和一个值为1的原子引用计数,并且把它们都存储在堆上的相同位置(紧挨着)。指向堆上的这份数据的指针存放在foo中。因此,foo是由指向一个对象的指针构成,被指向的对象包含vec![0]和原子计数。

当你调用let bar = Arc::clone(&foo)时,你是在获取foo的一个引用、对foo(指向存放在堆上的数据的指针)解引用、接着找到foo指向的地址、找出里面存放的值(vec![0]和原子计数)、把原子计数加一、最后把指向vec![0]的指针保存在bar中。

foobar离开作用域时,Arc::drop()就被调用了,原子计数减一。如果Arc::drop()发现原子计数等于0,那么它所指向的堆上的数据(vec![0]和原子计数)会被清理并从堆上擦除。

原子计数是一种能够让你以线程安全的方式修改和增加它的值的类型;在对原子类型允许进行其他操作之前,前面的原子类型操作必须要全部完成;因此被称为原子的(atomic)(即不可分割的)(操作)。

需要注意的是,Arc只能包含不可变数据。这是因为如果两个线程试图在同一时间修改被包含的值,Arc无法保证避免数据竞争。如果你希望修改数据,你应该在Arc类型内部封装一个互斥锁保护(Mutex guard)。

为什么这些东西能让Arc是线程安全的呢?

Arc是线程安全的是因为它给编译器保证数据的引用至少活得和数据本身一样长(译注:这里原作者应该是想表达,数据的引用存在期间,数据都是有效的)。这是因为每次你创建一个对堆上数据得引用,原子计数就会加一,数据只有在当原子计数等于零得时候才会被丢弃(每当一个引用离开作用域时,原子计数会减一)——Arc和一个普通得Rc(引用计数)之间得区别就在于原子计数。

那么Rc有什么用,为什么不用Arc来做所有事情?

原因是,原子计数是一个(开销)昂贵的变量类型,而普通的usize类型则没有这些开销。原子类型不仅在实际的程序中占用更多的内存,而且每个原子类型的操作还需要更长的时间,因为它必须分配资源来为对其自身进行读写的调用维护一个队列进而保证原子性。

【译】Arc 在 Rust 中是如何工作的的更多相关文章

  1. 【译】理解Rust中的Futures (一)

    原文标题:Understanding Futures In Rust -- Part 1 原文链接:https://www.viget.com/articles/understanding-futur ...

  2. 【译】理解Rust中的Futures(二)

    原文标题:Understanding Futures in Rust -- Part 2 原文链接:https://www.viget.com/articles/understanding-futur ...

  3. 【译】理解Rust中的闭包

    原文标题:Understanding Closures in Rust 原文链接:https://medium.com/swlh/understanding-closures-in-rust-21f2 ...

  4. 【译】理解Rust中的局部移动

    原文标题:Understanding Partial Moves in Rust 原文链接:https://whileydave.com/2020/11/30/understanding-partia ...

  5. 【译】为什么Rust中的BTreeMap没有with_capacity()方法?

    原文标题:Why doesn't Rust's BTreeMap have a with_capacity() method? 原文链接:https://www.nicolas-hahn.com/20 ...

  6. 【译】对Rust中的std::io::Error的研究

    原文标题:Study of std::io::Error 原文链接:https://matklad.github.io/2020/10/15/study-of-std-io-error.html 公众 ...

  7. 【译】深入理解Rust中的生命周期

    原文标题:Understanding Rust Lifetimes 原文链接:https://medium.com/nearprotocol/understanding-rust-lifetimes- ...

  8. 【译】Rust中的array、vector和slice

    原文链接:https://hashrust.com/blog/arrays-vectors-and-slices-in-rust/ 原文标题:Arrays, vectors and slices in ...

  9. 【译】关于Rust模块的清晰解释

    原文链接: http://www.sheshbabu.com/posts/rust-module-system/ 原文标题: Clear explanation of Rust's module sy ...

随机推荐

  1. python 读取文件时报错UnicodeDecodeError

    python 读取文件时报错UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 205: illegal multib ...

  2. pytest文档53-命令行实时输出错误信息(pytest-instafail)

    前言 pytest 运行全部用例的时候,在控制台会先显示用例的运行结果(.或F), 用例全部运行完成后最后把报错信息全部一起抛出到控制台. 这样我们每次都需要等用例运行结束,才知道为什么报错,不方便实 ...

  3. go 参数传递的是值还是引用 (转)

    https://blog.csdn.net/qq_16059847/article/details/104062759

  4. centos8上redis5在生产环境的配置

    一,创建redis的数据和日志目录: [root@yjweb data]# mkdir /data/redis6379 [root@yjweb data]# mkdir /data/redis6379 ...

  5. 列表的嵌套,元组和range()方法

    列表嵌套: 列表内嵌套列表 li = ['a','b',[1,2,3,["李白",'苏轼'],4,5],'c'] #取出"李白" print(li[2][3][ ...

  6. PHP实现Bitmap的探索 - GMP扩展使用

    原文地址:https://blog.fanscore.cn/p/22/ 一.背景 公司当前有一个用户群的系统,核心功能是根据不同的条件组去不同的业务线中get符合条件的uid列表,然后存到redis中 ...

  7. 关于 Deployer 部署结构

    Deployer 部署完成后,在服务器上的结构会是这样子: drwxr-sr-x 5 deployer www-data 4096 Jun 14 09:53 ./ drwxr-sr-x 6 deplo ...

  8. GridView使用SimpleAdapter

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app=&q ...

  9. Redis基础(二)数据库

    数据库 Redis服务器的所有数据库都保存在redisServer.db数组中,而数据库的数量则由redisServer.dbnum属性保存. struct redisServer { // .. / ...

  10. MAP;MLE

    判别学习算法:直接对问题进行求解,比如二分类问题,都是在空间中寻找一条直线从而把类别的样例分开,对于新的样例只要判断在直线的那一侧就可. ==>这种直接求解的方法称为判别学习方法 生成学习算法: ...