Rc与Arc实现1vN所有权机制

Rc与Arc实现1vN所有权机制

    • 观察引用计数的变化
    • 一个例子
    • 多线程无力的Rc< T >
    • Arc

Rust所有权机制要求一个值只能有一个所有者,在大多数情况下,都没有问题,但是考虑以下情况:

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

为了解决这种问题,Rust通过引用计数的方式,允许一个数据资源在同一时刻拥有多个所有者。这种实现机制就是Rc和Arc,前者适用于单线程,后者适用于多线程。二者大部分情况下都相同。

fn main() {let s = String::from("hello, world");// s在这里被转移给alet a = Box::new(s);// 报错!此处继续尝试将 s 转移给 blet b = Box::new(s);
}

采用Rc就能解决

use std::rc::Rc;
fn main() {let a = Rc::new(String::from("hello, world"));let b = Rc::clone(&a);assert_eq!(2, Rc::strong_count(&a));assert_eq!(Rc::strong_count(&a), Rc::strong_count(&b))
}

使用 Rc::new 创建了一个新的 Rc< String > 智能指针并赋给变量 a,该指针指向底层的字符串数据。智能指针 Rc< T > 在创建时,还会将引用计数加 1,此时获取引用计数的关联函数 Rc::strong_count 返回的值将是 1。

接着,我们又使用 Rc::clone 克隆了一份智能指针 Rc< String >,并将该智能指针的引用计数增加到 2。由于 a 和 b 是同一个智能指针的两个副本,因此通过它们两个获取引用计数的结果都是 2。这里的clone是浅拷贝,仅仅复制了只能指针并增加了引用计数,没有克隆底层数据。

观察引用计数的变化

use std::rc::Rc;
fn main() {let a = Rc::new(String::from("test ref counting"));println!("count after creating a = {}", Rc::strong_count(&a));let b =  Rc::clone(&a);println!("count after creating b = {}", Rc::strong_count(&a));{let c =  Rc::clone(&a);println!("count after creating c = {}", Rc::strong_count(&c));}println!("count after c goes out of scope = {}", Rc::strong_count(&a));count after creating a = 1
count after creating b = 2
count after creating c = 3
count after c goes out of scope = 2
}

有几点值得注意:

  • 由于变量 c 在语句块内部声明,当离开语句块时它会因为超出作用域而被释放,所以引用计数会减少 1,事实上这个得益于 Rc 实现了 Drop 特征
  • a、b、c 三个智能指针引用计数都是同样的,并且共享底层的数据,因此打印计数时用哪个都行
  • 无法看到的是:当 a、b 超出作用域后,引用计数会变成 0,最终智能指针和它指向的底层字符串都会被清理释放

事实上,RC< T >是指向底层数据的不可变引用,因此你无法通过它来修改数据,因为Rust的借用规则:要么存在多个不可变借用,要么只存在一个可变借用。

一个例子

use std::rc::Rc;struct Owner {name: String,// ...其它字段
}struct Gadget {id: i32,owner: Rc<Owner>,// ...其它字段
}fn main() {// 创建一个基于引用计数的 `Owner`.let gadget_owner: Rc<Owner> = Rc::new(Owner {name: "Gadget Man".to_string(),});// 创建两个不同的工具,它们属于同一个主人let gadget1 = Gadget {id: 1,owner: Rc::clone(&gadget_owner),};let gadget2 = Gadget {id: 2,owner: Rc::clone(&gadget_owner),};// 释放掉第一个 `Rc<Owner>`drop(gadget_owner);// 尽管在上面我们释放了 gadget_owner,但是依然可以在这里使用 owner 的信息// 原因是在 drop 之前,存在三个指向 Gadget Man 的智能指针引用,上面仅仅// drop 掉其中一个智能指针引用,而不是 drop 掉 owner 数据,外面还有两个// 引用指向底层的 owner 数据,引用计数尚未清零// 因此 owner 数据依然可以被使用println!("Gadget {} owned by {}", gadget1.id, gadget1.owner.name);println!("Gadget {} owned by {}", gadget2.id, gadget2.owner.name);// 在函数最后,`gadget1` 和 `gadget2` 也被释放,最终引用计数归零,随后底层// 数据也被清理释放
}

Rc简单总结

  • Rc/Arc 是不可变引用,你无法修改它指向的值,只能进行读取,如果要修改,需要配合后面章节的内部可变性 RefCell 或互斥锁 Mutex
  • 一旦最后一个拥有者消失,则资源会自动被回收,这个生命周期是在编译期就确定下来的
  • Rc 只能用于同一线程内部,想要用于线程之间的对象共享,你需要使用 Arc
  • Rc< T > 是一个智能指针,实现了 Deref 特征,因此你无需先解开 Rc 指针,再使用里面的 T,而是可以直接使用 T,例如上例中的 gadget1.owner.name

多线程无力的Rc< T >

use std::rc::Rc;
use std::thread;fn main() {let s = Rc::new(String::from("多线程漫游者"));for _ in 0..10 {let s = Rc::clone(&s);let handle = thread::spawn(move || {println!("{}", s)});}
}error[E0277]: `Rc<String>` cannot be sent between threads safely

表面原因是 Rc< T > 不能在线程间安全的传递,实际上是因为它没有实现 Send 特征,而该特征是恰恰是多线程间传递数据的关键,我们会在多线程章节中进行讲解。

当然,还有更深层的原因:由于 Rc< T > 需要管理引用计数,但是该计数器并没有使用任何并发原语,因此无法实现原子化的计数操作,最终会导致计数错误。

好在天无绝人之路, Rust 为我们提供的功能类似但是多线程安全的 Arc。

Arc

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

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

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

use std::sync::Arc;
use std::thread;fn main() {let s = Arc::new(String::from("多线程漫游者"));for _ in 0..10 {let s = Arc::clone(&s);let handle = thread::spawn(move || {println!("{}", s)});}
}

ArcRc 并没有定义在同一个模块,前者通过 use std::sync::Arc 来引入,后者通过 use std::rc::Rc

这两者都是只读的,如果想要实现内部数据可修改,必须配合内部可变性 RefCell 或者互斥锁 Mutex 来一起使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/138204.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C# .NET Core API Controller以及辅助专案

准备工作 Windows 10Visual Studio 2019(2017就有可以集中发布到publish目录的功能了吧)C#将方法封装(据说可以提高效率,就像是我们用的dll那种感觉新增专案作为我们API的辅助专案(作用类似dll&#xff0c;此处&#xff0c;你也可以在你自己的API专案里建文件夹&#xff0c;但…

MySQL---存储过程

存储过程的相关概念 是一组为了完成特定功能的sql语句的集合&#xff0c;类似于函数 写好了一个存储过程之后&#xff0c;我们可以像函数一样随时调用sql的集合。 复杂的&#xff0c;需要很多sql语句联合执行完成的任务 存储过程再执行上比sql语句的执行速度更快&#xff0c…

js 求数组中的对象某个属性和

可以直接看下效果 代码&#xff1a; <script>let list [{num: 1,price: 10,},{num: 2,price: 10,},{num: 3,price: 10,},{num: 4,price: 10,},]// for循环 求总数和 num的和let num 0for (let i 0; i < list.length; i) {num list[i].num}console.log(第一种&am…

学习c#的第三天

目录 C# 数据类型 值类型&#xff08;Value types&#xff09; 引用类型&#xff08;Reference types&#xff09; 指针类型&#xff08;Pointer types&#xff09; C# 类型转换 隐式类型转换 显式类型转换 类型之间的转换 - Convert 和 Parse Convert.ToInt32() 与 i…

AirTag追踪汽车

美国华盛顿特区&#xff0c;11月4日&#xff0c;在一项全新的抗击车辆盗窃的措施中&#xff0c;市长穆里尔•鲍泽签署了一项新计划&#xff0c;将向该市车辆盗窃频率较高的社区居民免费提供苹果AirTag追踪器。 AirTag是苹果公司推出的一款蓝牙跟踪设备&#xff0c;它依靠Findm…

从单体到微服务:使用Spring Boot构建事件驱动的Java应用程序

Spring Boot是Pivotal团队设计的一种微服务框架&#xff0c; 基于Spring开发&#xff0c;用于简化新Spring应用的初始搭建及开发过程&#xff0c;提升Spring 开发者的体验。它秉持“约定大于配置”的思想&#xff0c;集成了大量开箱即用的第三方库&#xff0c;支持绝大多数开源…

asp.net学院网上报销系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio

一、源码特点 asp.net学院网上报销系统是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为sqlserver2008&#xff0c;使用c#语言 开发 asp.net学院网上报销系统 应用技术…

基于飞浆resnet50的102分类

目录 1.数据预处理 2.数据导入 3.模型导入 4.批训练 5. 输出结果 6.结果参考 1.数据预处理 Ttransforms.Compose([transforms.Resize((250,250)),transforms.RandomCrop(size224),transforms.RandomHorizontalFlip(0.5),transforms.RandomRotation(degrees15),transforms.Color…

navicat导入.sql文件出现:[ERR] 1067 - Invalid default value for ‘create_date‘

比较老的系统生成的数据库导入5.7时报错[Err] 1067 - Invalid default value for create_time 错误分析 表中的第一个TIMESTAMP列&#xff08;如果未声明为NULL或显示DEFAULT或ON UPDATE子句&#xff09;将自动分配DEFAULT CURRENT_TIMESTAMP和ON UPDATE CURRENT_TIMESTAMP属…

ElasticSearch文档分析

ElasticSearch文档分析 包含下面的过程&#xff1a; 将一块文本分成适合于倒排索引的独立的 词条将这些词条统一化为标准格式以提高它们的“可搜索性”&#xff0c;或者 recall 分析器执行上面的工作。分析器实际上是将三个功能封装到了一个包里&#xff1a; 字符过滤器 首先&a…

【性能测试】非GUI模式Jemter压测+TPS性能拐点详细,一篇带你打通...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 非GUI模式执行Jem…

RK3568驱动指南|第七篇-设备树-第64章 device_node转换成platform_device实验

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

SOLIDWORKS软件提供了哪些特征造型方法?硕迪科技

SOLIDWORKS作为一款三维设计软件&#xff0c;为用户提供了多种特征造型方法&#xff0c;以下是其中几种常用的&#xff1a; 实体建模特征&#xff1a;SOLIDWORKS使用实体建模技术来创建和编辑三维几何体。通过使用基本几何体&#xff08;如立方体、圆柱体、圆锥体等&#xff09…

Spring-Security前后端分离权限认证

前后端分离 一般来说&#xff0c;我们用SpringSecurity默认的话是前后端整在一起的&#xff0c;比如thymeleaf或者Freemarker&#xff0c;SpringSecurity还自带login登录页,还让你配置登出页,错误页。 但是现在前后端分离才是正道&#xff0c;前后端分离的话&#xff0c;那就…

@ConfigurationProperties使用

一直有个疑问,在使用ConfigurationProperties注解作用一个配置类时,如果该配置类继承了一个父类,那么父类的那些配置字段是否可以读取配置信息。 答案是可以的&#xff0c;前提是父类对应字段的set方法是public。 BaseProperties.java Getter Setter public class BasePropert…

for循环、while循环和do while循环有什么不同

for循环、while循环和do while循环是三种常见的循环语句&#xff0c;它们的主要不同点在于循环条件的判断时机和执行顺序。具体如下&#xff1a; for循环 for循环适用于已知循环次数的循环结构&#xff0c;它的语法格式为&#xff1a; for (初始化表达式; 循环条件; 更新表达…

深度学习之pytorch第一课

学习使用pytorch&#xff0c;然后进行简单的线性模型的训练与保存 学习代码如下&#xff1a; import numpy as np import torch import torch.nn as nn x_value [i for i in range(11)] x_train np.array(x_value,dtypenp.float32) print(x_train.shape) x_train x_train.r…

nodejs 异步架构

nodejs的核心之一就是非阻塞的异步IO&#xff0c;于是想知道它是怎么实现的&#xff0c;挖了下nodejs源码&#xff0c;找到些答案&#xff0c;在此跟大家分享下。首先&#xff0c;我用了一段js代码test-fs-read.js做测试&#xff0c;代码如下&#xff1a; var path require(pa…

React 递归手写流程图展示树形数据

需求 根据树的数据结构画出流程图展示&#xff0c;支持新增前一级、后一级、同级以及删除功能&#xff08;便于标记节点&#xff0c;把节点数据当作label展示出来了&#xff0c;实际业务中跟据情况处理&#xff09; 文件结构 初始数据 [{"ticketTemplateCode": &…