Rust中的并发性:Sync 和 Send Traits

在并发的世界中,最常见的并发安全问题就是数据竞争,也就是两个线程同时对一个变量进行读写操作。但当你在 Safe Rust 中写出有数据竞争的代码时,编译器会直接拒绝编译。那么它是靠什么魔法做到的呢?

这就不得不谈 Send 和 Sync 这两个标记 trait 了,实现 Send 的类型可以在多线程间转移所有权,实现 Sync 的类型可以在多线程间共享引用。但它们内部都是没有任何方法声明以及方法体的,二者仅仅是作为一个类型约束的标记信息提供给编译器,帮助编译器拒绝线程不安全的代码。

定义:

pub unsafe auto trait Send { }pub unsafe auto trait Sync { }

本文将深入探讨 SyncSend traits,了解为什么某些类型实现这些 traits,而另一些则没有,并讨论 Rust 中并发编程的最佳实践。

The Sync Trait

Sync trait 表示一个类型可以安全地被多个线程同时访问。这里的访问指的是只读共享安全。Rust 中几乎所有的原始类型都实现了 Sync trait

例如:

let x = 5; // i32 is Sync

i32 类型实现了 Sync ,所以在线程间共享 i32 值是安全的。

另一方面,提供内部可变性的类型(内部可变性指的是在拥有不可变引用的时候,依然可以获取到其内部成员的可变引用,进而对其数据进行修改。),如 Mutex<T> ,其中 T 未实现 Sync trait。

#[stable(feature = "rust1", since = "1.0.0")]
unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
#[stable(feature = "rust1", since = "1.0.0")]
unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}

因为 Mutex 使用锁来保护对内部数据的访问,如果多个线程同时访问它,可能会导致数据竞争或死锁。

举例来说:

use std::sync::Mutex;let m = Mutex::new(5); //Mutex<i32> is not Sync

Mutex<i32> 类型没有实现 Sync ,所以跨线程共享是不安全的。

为在多个线程安全地访问非 Sync 类型(如 Mutex<i32> ),我们必须使用适当的同步操作,如获取锁,执行操作和释放锁,在本文后面看到使用互斥锁和其他线程安全类型的示例。

支持 Sync 的类型

Rust 中的 Sync trait 确保了对同一数据的多个引用(无论是可变的还是不可变的)可以安全地从多个线程并发访问。任何实现 Sync trait 的类型 T 都可以被认为是“线程安全”的。

Rust 中的 Sync 类型的一些例子是:

  • 原始类型,如 i32boolchar 等。
  • 简单的聚合类型,如元组 (i32, bool)
  • 原子类型,如 AtomicBool

另一方面,非同步类型不能同时使用多个引用,因为这可能导致数据竞争。非同步类型的一些示例包括:

  • Mutex<i32> - 在访问内部 i32 之前需要锁定互斥体。
  • RefCell<i32> - 在访问内部值之前需要借用 RefCell。
  • Rc<i32> - 共享了内部 i32 的所有权,所以多个可变借用是不安全的。

非 Sync 类型多线程访问

Mutex

为在多个线程安全地访问非同步类型,我们需要使用同步原语,如互斥锁。若仅仅使用 Mutex 而不使用 Arc ,可使用像作用域线程(crossbeam),例如:

这里,我们使用 Mutex<i32> 来安全地从多个线程中修改和读取内部 String。 lock() 方法获取锁,阻止其他线程访问互斥体。

Atomic

AtomicU64 这样的原子类型也可以使用像 fetch_add() 这样的原子操作从多个线程安全地访问。例如:

总结

因此,总而言之,要在 Rust 中跨线程共享数据,数据必须:

  • 类型为 Sync (原始/不可变类型)
  • 封装在互斥或原子类型中(Mutex、RwLock、Atomic*)
  • 使用像通道这样的消息传递技术来跨线程传递数据的所有权。

The Send Trait

Rust 中的 Send trait 表示类型可以安全地跨线程边界传输。如果一个类型实现了 Send ,这意味着该类型的值的所有权可以在线程之间转移。

例如,像 i32bool 这样的原始类型是 Send

因为它们在线程之间共享时没有任何内部引用或可变而导致问题:

然而,像 Rc<i32> 这样的类型未实现 Send ,因为它的引用计数在内部发生了变化,并且多个线程改变相同的引用计数可能会导致内存不安全:

Rc<T> 这样的非 Send 类型不能跨线程传输,但它们仍然可以在单个线程中使用。当线程需要共享一些数据时,非 Send 类型可以被包装在像 Arc<T> 这样的线程安全的包装器中,Arc 使用原子操作来管理引用计数,并允许内部类型在线程之间共享。

总结一下,关于 Send 的几个关键点是:

  • 类型 Send 可以在线程之间转移所有权
  • i32bool 这样的原始类型是 Send
  • 具有内部可变的类型(如 Rc<T> )通常不是 Send
  • Send 类型仍然可以在单个线程中使用,或者在包装在像 Arc<T> 这样的线程安全的容器中时在线程之间共享
  • 跨线程传输非 Send 类型会导致未定义的行为和内存不安全

自定义实现 Sync 和 Send

要创建自定义类型 SyncSend ,您只需实现类型的 SyncSend trait。

这里有一个 持有裸指针*const u8MyBox 结构体, 由于只要复合类型中有一个成员不是 Send 或者 Sync,那么该类型也就不是 Send 或 Sync。裸指针*const u8 均未实现 SendSync TraitMyBox 复合类型也不是 SendSync

若给 MyBox 实现了 Send 和 Sync 则借助 Arc 可在线程间传递和共享数据。当然建议自己不要轻易去实现 Sync 和 Send Trait ,一旦实现就要为被实现类型的线程安全性负责。这件事本来就是一件很难保证的事情。

有些类型是不可能生成SyncSend的,因为它们包含非Sync/非Send类型或允许多线程的可变。例如, Rc<T>不能被设置为Send,因为引用计数需要被原子地更新,而RefCell<T>不能被设置为  Sync,因为它的借用检查不是线程安全的。

同步/发送规则和最佳实践

重要的是要记住混合Sync/Send  和非Sync/非Send类型的规则。一些需要遵守的关键规则:

类型必须是Send才能在线程之间移动。这意味着像Rc<T>这样的类型不能跨线程共享,因为它们不是Send 。

  • 如果一个类型包含一个非Send类型,那么外部类型不能是Send。例如Option<Rc<i32>>不是  Send ,因为Rc<i32>不是Send 。
  • Sync类型可以通过共享引用从多个线程并发使用。非Sync类型不能同时使用它们的值,并且一次只能在一个线程中可变。
  • 如果一个类型包含一个非Sync类型,那么外部类型不能是Sync。例如Mutex<Rc<i32>>不是Sync ,因为Rc<i32>不是Sync 。

并发 Rust 代码的一些最佳实践:

  • 尽可能避免可变。支持不可变的数据结构和逻辑。
  • 当需要修改时,使用同步原语(如Mutex<T>)来安全地从多个线程进行。
  • 使用消息传递在线程之间进行通信,而不是直接共享内存。这有助于避免数据竞争和未定义的行为。
  • 尽可能地限制为修改锁定数据的范围。持有锁太长时间会影响性能和吞吐量。
  • 根据它们是否实现SyncSend仔细选择类型。例如,在线程之间共享时,首选Arc<T>而不是  Rc<T> 。
  • 使用atomic类型进行简单的并发访问原语类型。它们允许从多个线程访问而不加锁。

参考链接

Concurrency in Rust: The Sync and Send Traits | by Technocrat | CoderHack.com | Medium

[基于 Send 和 Sync 的线程安全 - Rust 语言圣经(Rust Course)](https://course.rs/advance/concurrency-with-threads/send-sync.html "基于 Send 和 Sync 的线程安全 - Rust 语言圣经(Rust Course "基于 Send 和 Sync 的线程安全 - Rust 语言圣经(Rust Course)")")

Rust 中的 Arc 和 Mutex|关键在于 --- Arc and Mutex in Rust | It's all about the bit

Rust 入门与实践

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

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

相关文章

Spirng 当中 Bean的作用域

Spirng 当中 Bean的作用域 文章目录 Spirng 当中 Bean的作用域每博一文案1. Spring6 当中的 Bean的作用域1.2 singleton 默认1.3 prototype1.4 Spring 中的 bean 标签当中scope 属性其他的值说明1.5 自定义作用域&#xff0c;一个线程一个 Bean 2. 总结:3. 最后&#xff1a; 每…

Navicat 每次打开数据库时,总是弹出 “正在获取 ER 图表信息“

文章目录 1 问题描述2 问题截图3 解决办法 1 问题描述 使用 Navicat 打开数据库时&#xff0c;总是弹出 “正在获取 ER 图表信息”每次都弹出来&#xff0c;耗时长&#xff0c;有时候点 “取消” 还卡死&#xff0c;烦人 2 问题截图 获取表信息 解析为 ER 图&#xff08;最…

React正式更新!开始学习React 19!

本文为原创文章&#xff0c;原文链接&#xff1a;J实验室&#xff0c;未经授权请勿转载 今年2月份&#xff0c;React 发布消息确认今年发布 v19 版本&#xff0c;尘封两年的版本号终于要更新了&#xff08;详情点击&#xff1a;React 19 发布在即&#xff0c;抢先学习一下新特性…

打靶日记:midnight

前置 1. 下载靶机 前往https://www.vulnhub.com/&#xff0c;下载我们想要使用的靶机 本次实战使用的靶机是sunset: midnight 2. 导入VMware 我是用的是VM15&#xff0c;这里我们直接 点击文件-》打开-》选择我们下载完的文件&#xff08;如果是压缩包的话记得解压&#…

陪孩子终身成长

文章目录 自序 你必须成长&#xff0c;才能陪孩子成长1 理解养育的本质第1章 为什么说亲子关系决定孩子的一生亲子关系&#xff0c;决定了我们与世界的关系父母对孩子的影响是最大的所有关系都是原生家庭关系的投射我们的思维模式&#xff0c;由父母决定 第2章 远离劣质亲子…

编译工具各版本与操作系统版本号兼容性冷知识 : JetBrains IntelliJ IDEA 各个主要版本及其对应的操作系统版本号的兼容情况

编译工具各版本与操作系统版本号兼容性冷知识 &#x1f9e0;: JetBrains IntelliJ IDEA 各个主要版本及其对应的操作系统版本号的兼容情况 文章目录 编译工具各版本与操作系统版本号兼容性冷知识 &#x1f9e0;: JetBrains IntelliJ IDEA 各个主要版本及其对应的操作系统版本号…

codePen按钮样式学习

前言 看到codepen里面有的按钮搞得很炫酷&#xff0c;但其实也不是很难&#xff0c;就学习记录一下 逐渐出现边框 大体上来说就是当鼠标悬浮的时候触发四个transition&#xff0c;用after、before和span的after和before四个伪类做hover出来的边框 <div class"btn bt…

速成python

一个只会c的苦手来总结一下py的语法。没有其他语法基础的不建议看 1. 输入输出 print自带换行&#xff0c;可以写print("Hi", end"")取消换行 a input(你好:) # 默认是str print(type(a)) # 输出a的类型 a int(input()) # 或者a int(a) print(type(…

华为海思校园招聘-芯片-数字 IC 方向 题目分享——第七套

华为海思校园招聘-芯片-数字 IC 方向 题目分享——第七套 (共9套&#xff0c;有答案和解析&#xff0c;答案非官方&#xff0c;未仔细校正&#xff0c;仅供参考&#xff09; 部分题目分享&#xff0c;完整版获取&#xff08;WX:didadidadidida313&#xff0c;加我备注&#x…

前端页面单元测试最佳策略:全面涵盖逻辑、组件、流程、UI及性能优化测试,全面保障软件应用的质量

页面级别的测试要求我们从更宏观的角度审视应用&#xff0c;不仅关注单个组件的正确性&#xff0c;还要确保组件间的协作无误&#xff0c;以及用户在应用中的完整体验。通过集成测试、E2E测试和场景测试&#xff0c;我们可以更全面地覆盖应用的各种使用情况&#xff0c;提高软件…

深入浅出一文图解Vision Mamba(ViM)

文章目录 引言&#xff1a;Mamba第一章&#xff1a;环境安装1.1安装教程1.2问题总结1.3安装总结 第二章&#xff1a;即插即用模块2.1模块一&#xff1a;Mamba Vision代码&#xff1a;models_mamba.py运行结果 2.2模块二&#xff1a;MambaIR代码&#xff1a;MambaIR运行结果 第三…

深入浅出TCP 与 UDP

&#x1f525; 引言 在互联网的广阔天地里&#xff0c;TCP&#xff08;Transmission Control Protocol&#xff09;和UDP&#xff08;User Datagram Protocol&#xff09;作为传输层的两大支柱&#xff0c;各自承担着不同的使命。下面这篇文章将带你从基础到进阶&#xff0c;全…

什么是 Java 集合,Java 集合有几类?

Java集合&#xff08;Java Collections&#xff09;是Java编程语言中一组用于存储和操作数据的框架。它提供了一种便捷的方式来管理和操作数据集合&#xff0c;无论是数组还是其他数据结构。Java集合框架被设计为通用的、可扩展的&#xff0c;并且具有高性能。它为开发人员提供…

解决Linux CentOS 7安装了vim编辑器却vim编辑器不起作用、无任何反应

文章目录 前言一、解决vim不起作用&#xff08;卸载重新安装&#xff09;1.重新安装vim2.测试vim是否能正常使用 二、解决vim: error while loading shared libraries: /lib64/libgpm.so.2: file too short报错三、解决vim编辑器不能使用方向键和退格键问题 remove vim-common …

线上线下收银一体化,新零售POS系统引领连锁门店数字化转型-亿发

在市场竞争日益激烈的背景下&#xff0c;没有哪个商家能够永远屹立不倒。随着互联网技术的快速发展&#xff0c;传统的线下门店面临着来自电商和新零售的新型挑战。实体零售和传统电商都需要进行变革&#xff0c;都需要实现线上线下的融合。 传统零售在客户消费之后就与商家失…

Java 为什么设计成 “String” 不能用 “==” 比较值?

Java中的String是一种特殊的对象类型&#xff0c;用于表示字符串。在Java中&#xff0c;String对象的创建和比较是一个重要的话题&#xff0c;其中&#xff0c;操作符在比较String对象时有着特殊的行为。为了了解Java为什么设计成String不能用比较值&#xff0c;需要深入探讨Ja…

数据结构八:线性表之循环队列的设计

上篇博客&#xff0c;学习了栈&#xff0c;我们可以知道他也是一种线性表&#xff0c;遵从先进后出的原则&#xff0c;在本节&#xff0c;我们进一步学习另一种线性表—队列。就像饭堂里排队打饭的的队伍&#xff0c;作为一种先进先出的线性表&#xff0c;他又有哪些特别之处呢…

公网ip申请ssl仅260

现在很多网站都已经绑定域名&#xff0c;因此使用的都是域名SSL证书保护网站传输信息安全&#xff0c;而没有绑定域名只有公网IP地址的网站想要保护传输信息安全就要申请IP SSL证书。IP SSL证书也是由正规CA认证机构颁发的数字证书&#xff0c;用来保护用户的隐私以及数据安全&…

FLUKE万用表17B+的电压档最大内阻

项目中遇到一个测量兆欧级别电阻两端电压的问题&#xff0c;发现按照上图中的电路搭建出来的电路测得的电压为8.25V左右&#xff0c;按理说应为9V才对&#xff0c;后来想到万用表测量电压档不同的档位会有不同内阻&#xff0c;测量的电阻应远小于万用表电压档内阻才有效。本次测…

Creo Assembly “Save As“时,为什么关联的Drawing无法Save As

问题描述&#xff1a; Creo Assembly 进行“另存为”&#xff0c;勾选了“Copy Drawings”。但操作结果是&#xff0c;该Assembly相关联的Drawing没有被“另存为”。 原因分析&#xff1a; 查看Workspace&#xff0c;发现该Assembly a.asm相关联的Drawing为b.drw&#xff0…