原文标题: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与嵌入式的火花

    一.前言 近些年来python非常流行,Python是一种面向对象的解释性计算机程序设计语言,Python语法简介清晰,易读性以及可扩展性,Python具有丰富和强大的库,能够把用其他语言制作的各种模 ...

  2. selenium登录163邮箱,得到cookie,requests后续请求

    1.场景 很多时候登录操作是比较复杂的,因为存在各种反爆破操作,以及为了安全性提交数据都会存在加密.如果要完全模拟代码去实现登录操作是比较复杂,并且该网站后续更新了登录安全相关功能,那么登录的模拟操作 ...

  3. docker registry 记录

    部署 运行下面命令获取registry镜像 docker pull registry 下载到的版本默认为 docker.io/registry latest 将registry镜像运行并生成一个容器 ...

  4. C语言从1打印到100再打印到1该如何编写?我只服最后一种写法!

    我觉得这是一个送分题,奈何人才太多了,给出了各种古怪的写法,如果是做项目的话,我比骄建议一些正常的写法,就是大家都能看得懂的,不要搞什么花里胡哨,不过你要是交流的话,既然是交流,我不觉得要多正规,即使 ...

  5. C++ 多线程 std::thread 使用总结

    在C++ 11之前,官方并没有支持线程库.C++ 11通过标准库引入了对 thread 类的支持,大大方便了完成多线程开发的工作. std::thread 构造函数  (1)thread() noex ...

  6. 跟我一起学.NetCore之MVC过滤器,这篇看完走路可以仰着头走

    前言 MVC过滤器在之前Asp.Net的时候就已经广泛使用啦,不管是面试还是工作,总有一个考点或是需求涉及到,可以毫不疑问的说,这个技术点是非常重要的: 在之前参与的面试中,得知很多小伙伴只知道有一两 ...

  7. Vue中监听 键盘事件及修饰符

    键盘事件: keyCode 实际值 48到57     0 - 9 65到90           a - z ( A-Z ) 112到135       F1 - F24 8            ...

  8. Go 包管理历史以及 Go mod 使用

    之前也写过 Go 管理依赖工具 godep 的使用,当时看 godep 使用起来还是挺方便,其原因主要在于有总比没有强.关于依赖管理工具其实还是想从头聊聊这个需求以及大家做这个功能的各种出发点. GO ...

  9. Class的继承

    Class 的继承 简介 Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多. class Point { } class ColorPoint ...

  10. E. Copying Data 解析(線段樹)

    Codeforce 292 E. Copying Data 解析(線段樹) 今天我們來看看CF292E 題目連結 題目 給你兩個陣列\(a,b\),有兩種操作:把\(a\)的一段複製到\(b\),或者 ...