原文标题: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中。

当foo或bar离开作用域时,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. day14 Pyhton学习

    一.迭代器-概念 可迭代协议:内部含有__iter__方法的值/变量都是可迭代的 如何得到一个迭代器:可迭代变量.__iter__()返回一个迭代器 迭代器协议:内部含有__iter__方法和__ne ...

  2. linux(centos8):安装prometheus服务端/node_exporter客户端(prometheus 2.18.1)

    一,prometheus的用途 Prometheus是一个开源的系统监控和警报工具包 相比其他监控系统,它更适用于微服务的体系架构 它使用各种专用exporter,用来实现对硬件/存储/数据库/web ...

  3. rsync安装使用中出现的报错

    一,查看本地centos的版本: [root@localhost lib]# cat /etc/redhat-release CentOS Linux release 8.1.1911 (Core) ...

  4. ansible使用setup模块查看受控机的信息(ansible2.9.5)

    一,ansible的setup模块的用途: 用来获取主机的信息 说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest 对应的 ...

  5. 基于Docker的MySql

    MySQL Server安装教程 考虑到实际情况需要经常使用MySQL,为了方便大家能够快速基于Docker搭建MySQL这里以Linux下为例 进行说明,对于Windows用户来说直接通过查看官网H ...

  6. Linux命令提示符

    命令提示符:prompt [root@localhost ~]# 用户@主机名 所在目录 用户身份(#管理员 $普通用户) 显示提示符格式 Ubuntu sun@u18-2:~$ echo $PS1 ...

  7. Java进阶面试

    消息中间件: 1.你们公司生产环境用的是什么消息中间件? https://mp.weixin.qq.com/s?__biz=MzU0OTk3ODQ3Ng==&mid=2247484149&am ...

  8. IPEX-1代/3代/4代/5代,PCB天线底座,公头,样式及封装尺寸图

    1.IPEX-1代,PCB天线底座 2.IPEX-3代,PCB天线底座 3.IPEX-4代,PCB天线底座 4.IPEX-5代,PCB天线底座

  9. Vue、Node全栈项目~面向小白的博客系统~

    个人博客系统 前言 ❝ 代码质量问题轻点喷(去年才学的前端),有啥建议欢迎联系我,联系方式见最下方,感谢! 页面有啥bug也可以反馈给我,感谢! 这是一套包含前后端代码的个人博客系统,欢迎各位提出建议 ...

  10. Spring MVC 9大组件概述

    SpringMVC中的Servlet一共有三个层次,分别是HttpServletBean.FrameworkServlet和 DispatcherServlet.HttpServletBean直接继承 ...