Rust中的并发性:Sync 和 Send Traits

在并发的世界中,最常见的并发安全问题就是数据竞争,也就是两个线程同时对一个变量进行读写操作。但当你在 Safe Rust 中写出有数据竞争的代码时,编译器会直接拒绝编译。那么它是靠什么魔法做到的呢?
这就不得不谈 Send 和 Sync 这两个标记 trait 了,实现 Send 的类型可以在多线程间转移所有权,实现 Sync 的类型可以在多线程间共享引用。但它们内部都是没有任何方法声明以及方法体的,二者仅仅是作为一个类型约束的标记信息提供给编译器,帮助编译器拒绝线程不安全的代码。
定义:
pub unsafe auto trait Send { }pub unsafe auto trait Sync { }
本文将深入探讨 Sync 和 Send 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 类型的一些例子是:
- 原始类型,如
i32、bool、char等。 - 简单的聚合类型,如元组
(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 ,这意味着该类型的值的所有权可以在线程之间转移。
例如,像 i32 和 bool 这样的原始类型是 Send ,

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


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


像 Rc<T> 这样的非 Send 类型不能跨线程传输,但它们仍然可以在单个线程中使用。当线程需要共享一些数据时,非 Send 类型可以被包装在像 Arc<T> 这样的线程安全的包装器中,Arc 使用原子操作来管理引用计数,并允许内部类型在线程之间共享。
总结一下,关于 Send 的几个关键点是:
- 类型
Send可以在线程之间转移所有权 - 像
i32和bool这样的原始类型是Send - 具有内部可变的类型(如
Rc<T>)通常不是Send - 非
Send类型仍然可以在单个线程中使用,或者在包装在像Arc<T>这样的线程安全的容器中时在线程之间共享 - 跨线程传输非
Send类型会导致未定义的行为和内存不安全
自定义实现 Sync 和 Send
要创建自定义类型 Sync 或 Send ,您只需实现类型的 Sync 和 Send trait。
这里有一个 持有裸指针*const u8 的 MyBox 结构体, 由于只要复合类型中有一个成员不是 Send 或者 Sync,那么该类型也就不是 Send 或 Sync。裸指针*const u8 均未实现 Send 和 Sync Trait 故 MyBox 复合类型也不是 Send 或 Sync。
若给 MyBox 实现了 Send 和 Sync 则借助 Arc 可在线程间传递和共享数据。当然建议自己不要轻易去实现 Sync 和 Send Trait ,一旦实现就要为被实现类型的线程安全性负责。这件事本来就是一件很难保证的事情。


有些类型是不可能生成Sync和Send的,因为它们包含非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>)来安全地从多个线程进行。 - 使用消息传递在线程之间进行通信,而不是直接共享内存。这有助于避免数据竞争和未定义的行为。
- 尽可能地限制为修改锁定数据的范围。持有锁太长时间会影响性能和吞吐量。
- 根据它们是否实现
Sync和Send仔细选择类型。例如,在线程之间共享时,首选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 入门与实践
相关文章:
Rust中的并发性:Sync 和 Send Traits
在并发的世界中,最常见的并发安全问题就是数据竞争,也就是两个线程同时对一个变量进行读写操作。但当你在 Safe Rust 中写出有数据竞争的代码时,编译器会直接拒绝编译。那么它是靠什么魔法做到的呢? 这就不得不谈 Send 和 Sync 这…...
|Python新手小白中级教程|第二十七章:面向对象编程(示例操作)(3)使用turtle库与类结合
文章目录 前言一、项目:使用类Circle画出圆形(不调用turtle库)1.基础指令class2.使用turtle画出大圆与小圆3.使用其他功能画一只眼睛 二、使用turtle库画正方形总结 前言 hello,我是BoBo仔,welcome来看我的文章 这节课…...
Android OpenMAX(五)高通OMX Core实现
上一节了解了OMX Core提供的内容,这一节我们看看高通OMX Core是如何实现的。本节代码参考自: omx_core_cmp.cpp registry_table_android.c qc_omx_core.h 1、OMX_Init/OMX_Deinit OMX_API OMX_ERRORTYPE OMX_APIENTRY OMX_Init() {DEBUG_PRINT(...
XXE漏洞
一、概述 1、XXE:XML外部实体注入攻击 2、XML:可扩展标记语言。 (1)没有固定标签,所有标签都可以自定义,但有限制规则。 (2)用于数据对的传输与存储,常被用于充当配置文件 推荐教程:XML 教程 (3)后缀…...
[华为OD]C卷 BFS 亲子游戏 200
题目: 宝宝和妈妈参加亲子游戏,在一个二维矩阵(N*N)的格子地图上,宝宝和妈妈抽签决定各自 的位置,地图上每个格子有不同的Q糖果数量,部分格子有障碍物。 游戏规则Q是妈妈必须在最短的时间&a…...
大模型微调实战之强化学习 贝尔曼方程及价值函数(五)
大模型微调实战之强化学习 贝尔曼方程及价值函数(五) 现在, 看一下状态-动作值函数的示意图: 这个图表示假设首先采取一些行动(a)。因此,由于动作(a),代理可能会被环境转换到这些状…...
初探MFC程序混合使用QT
一、背景 随着操作系统国产化替代的趋势越发明显,软件支持国际化、跨平台,已然是必须做的一件事情。原有的软件UI层用的是MFC,将其换成QT,想必是一种较好的方案。对于大型软件,特别是已发布,但还处于不断迭…...
【LeetCode题库】1068. 产品销售分析 I —— MySQL 性能提升,using()关键字
文章目录 原题题解解题笔记 —— JOIN USING()关键字对性能的提升 我是一名立志把细节都说清楚的博主,欢迎【关注】🎉 ~ 原创不易, 如果有帮助 ,记得【点赞】【收藏】 哦~ ❥(^_-)~ 如有错误、疑惑,欢迎【评论】指正…...
leetcode 1 ~ 100
文章目录 1. 两数之和(用哈希表减少查找的时间复杂度)2. 两数相加(高精度加法)3.无重复字符的最长子串:(模板:经典的滑动窗口算法)5. 最长回文子串(枚举)6. Z…...
分享6个免费下载电子书的网站
着急看书的宝子们看这里! 收藏了一堆电子书网站终于能派上用场了~ 01/Z-Library https://zh.zlibrary-be.se/ 世界上最大的电子图书馆,拥有超千万的书籍和文章资源,99%的书籍资料都能在这里找到。 我给的这个网址现在还能正常打开使用&…...
typescript的入门到吐槽:看了typescript,发现前端真的卷,
typescript TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。 TypeScript 与 JavaScript 的区别 其实就是对JavaScript的封装,把一个弱类型语言封…...
抖店商品详情API接口,商品上架(主图,价格,sku等属性,)item_get-获得抖店商品详情
抖店商品详情API接口,商品上架(主图,价格,sku等属性,)item_get-获得抖店商品详情 {"code": 0,"msg": "调用成功","time": "1715166889","data&quo…...
STM32使用ADC单/多通道检测数据
文章目录 1. STM32单片机ADC功能详解 2. AD单通道 2.1 初始化 2.2 ADC.c 2.3 ADC.h 2.4 main.c 3. AD多通道 3.1 ADC.c 3.2 ADC.h 3.3 main.c 3.4 完整工程文件 1. STM32单片机ADC功能详解 STM32单片机ADC功能详解 2. AD单通道 这个代码实现通过ADC功能采集三脚电…...
Unity 性能优化之动态批处理(四)
提示:仅供参考,有误之处,麻烦大佬指出,不胜感激! 文章目录 前言一、动态合批是什么?二、使用动态批处理1.打开动态合批2.满足条件 三、检查动态合批是否成功五、动态合批弊端总结 前言 动态批处理是常用优…...
Windows 11 系统安装时如何跳过联网和逃避微软账号登录
问题描述 Windows 11 是从 22H2 版本之后开始强制联网何登录微软账号的。 这就带来两个问题: 1、如果我的电脑没有网络或者网卡驱动有问题,那就无法继续安装系统了。 2、如果我有强怕症,就是不想登录微软账号,害怕个人信息泄露…...
uniapp + vue3 使用axios
场景 uniapp自带的uni.request不太好用,也有可能是自己用axios用的太熟悉了,所以还是用axios趁手点,所以尝试在uniapp中使用axios。 操作 因为uniapp项目没有package.json,所以先在项目根目录下执行 npm init, 执行完毕后直接…...
关于前后端的参数传递
以前端javascript,后端nodejsexpress为例,后端可以从前端发来的request里获取这些属性:header、query、url、params、body,其中params和query是从url中解析获得,不过express已帮我们做好了,就不用我们自己再…...
华火电焰灶,科技打造“新”厨房
家里最大的空气污染源其实来自厨房里的燃气灶!——斯坦福大学发表的《科学进展》期刊 厨房在家庭中占有举足轻重的地位,它不仅是一个烹饪美食的场所,更是家人情感交流的重要空间。厨房大致经历了两次变革,分别是以柴火灶为主体的厨…...
普通人副业要趁早,5种靠谱且持久的赚钱副业
中年危机、35岁被裁,这些听起来就让人焦虑的词汇,是否也让你感到不安?别担心,只要你早早开启副业之旅,这些都不是问题。 今天,我要为你介绍的这5种副业,不仅能帮你赚钱,还能让你的能…...
【文献解析】3D高斯抛雪球是个什么玩意
论文地址:https://arxiv.org/abs/2308.04079 项目:3D Gaussian Splatting for Real-Time Radiance Field Rendering 代码:git clone https://github.com/graphdeco-inria/gaussian-splatting --recursive 一、文章概述 1.1问题导向 辐射…...
Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...
【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...
LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...
计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...
C++_哈希表
本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说,直接开始吧! 一、基础概念 1. 哈希核心思想: 哈希函数的作用:通过此函数建立一个Key与存储位置之间的映射关系。理想目标:实现…...
深入理解 React 样式方案
React 的样式方案较多,在应用开发初期,开发者需要根据项目业务具体情况选择对应样式方案。React 样式方案主要有: 1. 内联样式 2. module css 3. css in js 4. tailwind css 这些方案中,均有各自的优势和缺点。 1. 方案优劣势 1. 内联样式: 简单直观,适合动态样式和…...
Yii2项目自动向GitLab上报Bug
Yii2 项目自动上报Bug 原理 yii2在程序报错时, 会执行指定action, 通过重写ErrorAction, 实现Bug自动提交至GitLab的issue 步骤 配置SiteController中的actions方法 public function actions(){return [error > [class > app\helpers\web\ErrorAction,],];}重写Error…...
鸿蒙Navigation路由导航-基本使用介绍
1. Navigation介绍 Navigation组件是路由导航的根视图容器,一般作为Page页面的根容器使用,其内部默认包含了标题栏、内容区和工具栏,其中内容区默认首页显示导航内容(Navigation的子组件)或非首页显示(Nav…...
RabbitMQ 各类交换机
为什么要用交换机? 交换机用来路由消息。如果直发队列,这个消息就被处理消失了,那别的队列也需要这个消息怎么办?那就要用到交换机 交换机类型 1,fanout:广播 特点 广播所有消息:将消息…...
