Rust中的并发性:Sync 和 Send Traits

news2025/2/22 23:37:15

在并发的世界中,最常见的并发安全问题就是数据竞争,也就是两个线程同时对一个变量进行读写操作。但当你在 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.coloradmin.cn/o/1637022.html

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

相关文章

Apache SeaTunnel k8s 集群模式 Zeta 引擎部署指南

SeaTunnel提供了一种运行Zeta引擎(cluster-mode)的方法&#xff0c;可以让Kubernetes在本地运行Zeta引擎&#xff0c;实现更高效的应用程序部署和管理。在本文中&#xff0c;我们将探索SeaTunnel k8s运行zeta引擎(cluster-mode模式)的更多信息&#xff0c;了解如何更好地利用Ze…

大数据技术就业和发展前景怎么样

大数据技术的就业和发展前景极为乐观&#xff0c;具有行业需求旺盛、就业多样性、可持续发展潜力等特点&#xff0c; 上大学网 &#xff08;www.sdaxue.com&#xff09;整理出了大数据技术的就业和发展前景以下几个关键趋势&#xff0c;供大家参考&#xff01; 行业需求旺盛&…

[华为OD] C卷 5G网络 现需要在某城市进行5G网络建设,已经选取N个地点设置5G基站 200

题目 现需要在某城市进行5G网络建设&#xff0c;已经选取N个地点设置5G基站&#xff0c;编号固定为1到N,接 下来需要各个基站之间使用光纤进行连接以确保基站能互联互通&#xff0c;不同基站之间架设光纤的成 本各不相同&#xff0c;且有些节点之间已经存在光纤相连&#…

数据仓库Data Warehouse

数据仓库Data Warehouse 数仓是一种思想,数仓是一种规范,数仓是一种解决方案 1. 数据处理方式 数据处理大致可以分成两大类: 联机事务处理OLTP(on-line transaction processing)联机分析处理OLAP(On-Line Analytical Processing)1.1. OLTP OLTP的全称是On-line Transa…

Unity添加证件

目录 1.问题描述&#xff1a;2.解决方法&#xff1a;小结&#xff1a; 1.问题描述&#xff1a; 2.解决方法&#xff1a; 登录后跳转打开 添加证件 选择个人证件 小结&#xff1a; 关注我给大家分享更多有趣的知识&#xff0c;以下是个人公众号&#xff0c;提供 ||代码兼职||…

基于 React 的图形验证码插件

react-captcha-code NPM 地址 &#xff1a; react-captcha-code - npm npm install react-captcha-code --save 如下我自己的封装&#xff1a; import Captcha from "react-captcha-code";type CaptchaType {captchaChange: (captchaInfo: string) > void;code…

利用大型语言模型提升数字产品创新:提示,微调,检索增强生成和代理的应用

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

【漏洞复现】科达 MTS转码服务器 任意文件读取漏洞

0x01 产品简介 科达 MTS转码服务器是一款专业的视频转码设备&#xff0c;采用了高性能的硬件配置和先进的转码技术&#xff0c;能够实现高效、高质量的视频转码。 0x02 漏洞概述 科达 MTS转码服务器存在任意文件读取漏洞&#xff0c;攻击可以通过该漏洞读取服务器任意敏感信…

ChatGPT向付费用户推“记忆”功能,可记住用户喜好 | 最新快讯

4月30日消息&#xff0c;人工智能巨头OpenAI宣布&#xff0c;其开发的聊天机器人ChatGPT将在除欧洲和韩国以外的市场全面上线“记忆”功能。这使得聊天机器人能够“记住”ChatGPT Plus付费订阅用户的详细信息&#xff0c;从而提供更个性化的服务。 OpenAI早在今年2月就已经宣布…

java同步大量数据到本地数据库方法总结

最近在做一个需求&#xff0c;就是我需要对三方接口调用的数据存放到本地的数据库里的数据表里面。那么一开始我就是直接一条一条save&#xff0c;结果发现耗时非常严重&#xff0c;后面我就进行了改进。就是分批次去同步或者分批次去异步。 现在我直接贴出我写的代码&#xf…

PG后台进程个人解读和与oracle 的比较

1.background writer &#xff08;后台写进程&#xff09; 与OracleDBWR进程工作原理类似&#xff0c;都是负责把缓冲区里面的脏块写到数据文件中&#xff0c;写的目的有两个&#xff1a; 1.为了保存数据。 2.为了释放内存空间。 触发background writer 写的条件&#xff1a…

Virtualbox--下载指定版本

一、前言 下载Virtualbox7.0.10&#xff0c;可参考《Virtualbox–下载指定版本》 Virtualbox7.0.10具体安装步骤&#xff0c;可参考《Virtualbox7.0.10的安装步骤》 Virtualbox7.0.10创建虚拟机&#xff0c;可参考《Virtualbox7.0.10–创建虚拟机》 Virtualbox7.0.10安装Ubuntu…

GPT3 终极指南(二)

原文&#xff1a;zh.annas-archive.org/md5/6de8906c86a2711a5a84c839bec7e073 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第五章&#xff1a;GPT-3 作为企业创新的下一步 当一个新的创新或技术转变发生时&#xff0c;大公司通常是最后一个采纳的。它们的等级结构…

Linux 文件管理命令Lawk wc comm join fmt

文章目录 2.Linux 文件管理命令2.44 awk&#xff1a;模式匹配语言1&#xff0e;变量2&#xff0e;运算符3&#xff0e;awk 的正则4&#xff0e;字符串函数5&#xff0e;数学函数案例练习 2.45 wc&#xff1a;输出文件中的行数、单词数、字节数案例练习2.46 comm&#xff1a;比较…

Qwen-Audio:推动通用音频理解的统一大规模音频-语言模型(开源)

随着人工智能技术的不断进步&#xff0c;音频语言模型&#xff08;Audio-Language Models&#xff09;在人机交互领域变得越来越重要。然而&#xff0c;由于缺乏能够处理多样化音频类型和任务的预训练模型&#xff0c;该领域的进展受到了限制。为了克服这一挑战&#xff0c;研究…

Spring Data Redis简单使用

Spring Data Redis是一个用于简化应用程序与Redis交互的开发框架。它提供了简单的配置和方便的操作API&#xff0c;使得与Redis的集成变得更加容易。下面是一个快速入门使用Spring Data Redis的步骤&#xff1a; 步骤 1&#xff1a;添加依赖 在您的项目中添加Spring Data Redi…

掌握未来:打造高效、可靠系统的终极指南—RESTful API 设计详解

RESTful API 是一种软件架构风格和开发规范&#xff0c;它基于 Representational State Transfer&#xff08;REST&#xff09;原则。RESTful API 使用 HTTP 协议的标准方法&#xff0c;如 GET、POST、PUT、DELETE 等&#xff0c;来进行资源的创建、读取、更新和删除操作。这种…

四川易点慧电子商务抖音小店:创新引领,开启电商新篇章

随着互联网的快速发展&#xff0c;电子商务行业日新月异&#xff0c;抖音小店作为新兴的电商模式&#xff0c;正逐渐崭露头角。四川易点慧电子商务有限公司紧跟时代步伐&#xff0c;以创新思维引领抖音小店发展&#xff0c;成为了行业的佼佼者。 易点慧电子商务位于风景秀丽的四…

FFmpeg开发笔记(二十三)使用OBS Studio开启RTMP直播推流

OBS是一个开源的直播录制软件&#xff0c;英文全称叫做Open Broadcaster Software&#xff0c;广泛用于视频录制、实时直播等领域。OBS不但开源&#xff0c;而且跨平台&#xff0c;兼容Windows、Mac OS、Linux等操作系统。 OBS的官网是https://obsproject.com/&#xff0c;录制…

如何安全可控的进行跨区域数据交换,提高数据价值?

跨区域数据交换指的是在不同地理位置或不同网络环境下的数据传输和共享。随着数字化转型的加速&#xff0c;企业及组织越来越依赖于数据的流动来优化业务流程、增强决策制定和推动创新。然而&#xff0c;跨区域数据交换也带来了一系列的挑战和风险&#xff0c;主要包括&#xf…