Rust编程中的共享状态并发执行
1.共享状态并发
虽然消息传递是一个很好的处理并发的方式,但并不是唯一一个。另一种方式是让多个线程拥有相同的共享数据。在学习Go语言编程过程中大家应该听到过一句口号:"不要通过共享内存来通讯"。
在某种程度上,任何编程语言中的信道都类似于单所有权,因为一旦将一个值传送到信道中,将无法再使用这个值。共享内存类似于多所有权:多个线程可以同时访问相同的内存位置。第十五章介绍了智能指针如何使得多所有权成为可能,然而这会增加额外的复杂性,因为需要以某种方式管理这些不同的所有者。Rust 的类型系统和所有权规则极大的协助了正确地管理这些所有权。作为一个例子,让我们看看互斥器,一个更为常见的共享内存并发原语。
互斥器(mutex)是 mutual exclusion 的缩写,也就是说,任意时刻,其只允许一个线程访问某些数据。为了访问互斥器中的数据,线程首先需要通过获取互斥器的 锁(lock)来表明其希望访问数据。锁是一个作为互斥器一部分的数据结构,它记录谁有数据的排他访问权。因此,我们描述互斥器为通过锁系统 保护(guarding)其数据。
互斥器以难以使用著称,因为你不得不记住:
-
在使用数据之前尝试获取锁。
-
处理完被互斥器所保护的数据之后,必须解锁数据,这样其他线程才能够获取锁。
作为一个现实中互斥器的例子,想象一下在某个会议的一次小组座谈会中,只有一个麦克风。如果一位成员要发言,他必须请求或表示希望使用麦克风。一旦得到了麦克风,他可以畅所欲言,然后将麦克风交给下一位希望讲话的成员。如果一位成员结束发言后忘记将麦克风交还,其他人将无法发言。如果对共享麦克风的管理出现了问题,座谈会将无法如期进行!
正确的管理互斥器异常复杂,这也是许多人之所以热衷于信道的原因。然而,在 Rust 中,得益于类型系统和所有权,我们不会在锁和解锁上出错。
2.Mutex<T>的API
作为展示如何使用互斥器的例子,让我们从在单线程上下文使用互斥器开始, 看下面的代码:
use std::sync::Mutex;fn main() {let m = Mutex::new(5);{let mut num = m.lock().unwrap();*num = 6;}println!("m = {:?}", m);
}
像很多类型一样,我们使用关联函数 new 来创建一个 Mutex<T>。使用 lock 方法获取锁,以访问互斥器中的数据。这个调用会阻塞当前线程,直到我们拥有锁为止。
如果另一个线程拥有锁,并且那个线程 panic 了,则 lock 调用会失败。在这种情况下,没人能够再获取锁,所以这里选择 unwrap 并在遇到这种情况时使线程 panic。
一旦获取了锁,就可以将返回值(在这里是num)视为一个其内部数据的可变引用了。类型系统确保了我们在使用 m 中的值之前获取锁。m 的类型是 Mutex<i32> 而不是 i32,所以 必须 获取锁才能使用这个 i32 值。我们是不会忘记这么做的,因为反之类型系统不允许访问内部的 i32 值。
Mutex<T> 是一个智能指针。更准确的说,lock 调用 返回 一个叫做 MutexGuard 的智能指针。这个智能指针实现了 Deref 来指向其内部数据;其也提供了一个 Drop 实现当 MutexGuard 离开作用域时自动释放锁,为此,我们不会忘记释放锁并阻塞互斥器为其它线程所用的风险,因为锁的释放是自动发生的。
丢弃了锁之后,可以打印出互斥器的值,并发现能够将其内部的 i32 改为 6。
3.在线程间共享Mutex<T>
现在让我们尝试使用 Mutex<T> 在多个线程间共享值。我们将启动十个线程,并在各个线程中对同一个计数器值加一,这样计数器将从 0 变为 10。看下面的代码:
use std::sync::Mutex;
use std::thread;fn main() {let counter = Mutex::new(0);let mut handles = vec![];for _ in 0..10 {let handle = thread::spawn(move || {let mut num = counter.lock().unwrap();*num += 1;});handles.push(handle);}for handle in handles {handle.join().unwrap();}println!("Result: {}", *counter.lock().unwrap());
}
这里创建了一个 counter 变量来存放内含 i32 的 Mutex<T>, 接下来遍历 range 创建了 10 个线程。使用了 thread::spawn 并对所有线程使用了相同的闭包:它们每一个都将调用 lock 方法来获取 Mutex<T> 上的锁,接着将互斥器中的值加一。当一个线程结束执行,num 会离开闭包作用域并释放锁,这样另一个线程就可以获取它了。
在主线程中,我们收集了所有的 join 句柄, 调用它们的 join 方法来确保所有线程都会结束。这时,主线程会获取锁并打印出程序的结果。
编译上面的代码, Rust编译器报了一个错误:

错误信息表明 counter 值在上一次循环中被移动了。所以 Rust 告诉我们不能将 counter 锁的所有权移动到多个线程中。下面来看看如何修复这个错误。
4.多线程和多所有权
我们先尝试将Mutex<T>封装进Rc<T>中并在将所有权移入线程之前克隆Rc<T>,看下面代码:
use std::rc::Rc;
use std::sync::Mutex;
use std::thread;fn main() {let counter = Rc::new(Mutex::new(0));let mut handles = vec![];for _ in 0..10 {let counter = Rc::clone(&counter);let handle = thread::spawn(move || {let mut num = counter.lock().unwrap();*num += 1;});handles.push(handle);}for handle in handles {handle.join().unwrap();}println!("Result: {}", *counter.lock().unwrap());
}
再一次编译代码,纳尼, 居然又报了另一个错误, 成年人的崩溃谁能懂:

Rc<Mutex<i32>>` cannot be sent between threads safely`。这个错误编译器告诉我们原因是:`the trait `Send` is not implemented for `Rc<Mutex<i32>>。
Rc<T> 并不能安全的在线程间共享。当 Rc<T> 管理引用计数时,它必须在每一个 clone 调用时增加计数,并在每一个克隆被丢弃时减少计数。Rc<T> 并没有使用任何并发原语,来确保改变计数的操作不会被其他线程打断。在计数出错时可能会导致诡异的 bug,比如可能会造成内存泄漏,或在使用结束之前就丢弃一个值。我们所需要的是一个完全类似 Rc<T>,又以一种线程安全的方式改变引用计数的类型。
5.原子引用计数Arc<T>
在Rust标准库中, 提供了一个名为Arc<T>的类型, 这是一个可以安全的用于并发环境的类型, 字母 “a” 代表 原子性(atomic),所以这是一个 原子引用计数(atomically reference counted)类型, 将代码修改为:
use std::sync::{Arc, Mutex};
use std::thread;fn main() {let counter = Arc::new(Mutex::new(0));let mut handles = vec![];for _ in 0..10 {let counter = Arc::clone(&counter);let handle = thread::spawn(move || {let mut num = counter.lock().unwrap();*num += 1;});handles.push(handle);}for handle in handles {handle.join().unwrap();}println!("Result: {}", *counter.lock().unwrap());
}
再次编译代码, 执行结果如下:

这次终于得到结果10, 程序从0数到10, 虽然过程看上去并不明显, 但我们却学到了很多关于Mutex<T>和线程安全的内容。
相关文章:
Rust编程中的共享状态并发执行
1.共享状态并发 虽然消息传递是一个很好的处理并发的方式,但并不是唯一一个。另一种方式是让多个线程拥有相同的共享数据。在学习Go语言编程过程中大家应该听到过一句口号:"不要通过共享内存来通讯"。 在某种程度上,任何编程语言中的信道都类…...
python语法之数据类型
在python编程中,数据类型是一个重要的概念。 变量可以存储不同类型的数据,不同的类型可以做不同的事情。 Python在这些类别中默认内置了以下数据类型: 文本类型:str数值类型:int, float, complex序列类型:list, tup…...
Skybox天空盒子的更换教程_unity基础开发教程
Skybox天空盒子的更换 Skybox的下载与导入更换SkyboxSkybox属性自定义 Skybox的下载与导入 打开资源商店 搜索FREE Skybox 这里是我使用的是这一款资源,点击添加至我的资源 打开包管理器Package Manager Packages选择My Assets 搜索Sky 选择刚刚添加的天空盒子 点…...
Android模拟器的linux内核源码的下载
文章目录 Android模拟器的linux内核源码的下载 Android模拟器的linux内核源码的下载 git clone https://aosp.tuna.tsinghua.edu.cn/android/kernel/goldfish.git自己新建一个文件夹存放内核代码,命名随意。 切换一下分支就有东西了 切换到下面这个分支...
Vue中methods实现原理
目录 前言 回调函数中的this指向问题 vue实例访问methods methods实现原理 前言 vue实例对象为什么可以访问methods中的函数方法?methods的实现原理是什么? 回调函数中的this指向问题 在解答前言中的问题前,需要了解一下回调函数中的th…...
维基百科是非营利性机构 词条内容具有中立性、准确性、可靠性
维基百科对一些企业很有神秘性,自行操作很多次也没有成功建立维基百科,这一定是没有按照维基百科的规则和流程去操作。小马识途营销顾问提醒企业,维基百科是一种基于协作的在线百科全书,由维基媒体基金会运营。维基百科的创建流程…...
C/C++轻量级并发TCP服务器框架Zinx-框架开发002: 定义通道抽象类
文章目录 2 类图设计3 时序图数据输入处理:输出数据处理总流程 4 主要实现的功能4.1 kernel类:基于epoll调度所有通道4.2 通道抽象类:4.3 标准输入通道子类4.4 标准输出通道子类4.5 kernel和通道类的调用 5 代码设计5.1 框架头文件5.2 框架实…...
bin、hex、ELF文件格式上的区别
bin, hex, 和 ELF 是三种不同的文件格式,主要用于表示和存储二进制数据和程序代码。它们各自有其用途、特点和格式: bin (Binary) 文件: 通常表示纯二进制格式的文件。它不包含任何元数据或文件结构,只是简单地按照字节顺序存储数据。这种文件…...
《QT从基础到进阶·二十六》绘制多个图形项(QGraphicsRectItem,QGraphicsLineItem,QGraphicsPolygonItem)
这个demo用QT实现了对多个图形项的绘制,包括矩形的绘制,直线的绘制和多边形的绘制,是之前一章中绘制矩形的增强版,之前一章节关于矩形的绘制可以参考:《QT从基础到进阶十五》用鼠标绘制矩形(QGraphicsView、…...
【分布式】CAP理论详解
一、CAP理论概述 在分布式系统中,CAP是指一组原则,它们描述了在网络分区(Partition)时,分布式系统能够提供的保证。CAP代表Consistency(一致性)、Availability(可用性)和…...
AI歌姬,C位出道,基于PaddleHub/Diffsinger实现音频歌声合成操作(Python3.10)
懂乐理的音乐专业人士可以通过写乐谱并通过乐器演奏来展示他们的音乐创意和构思,但不识谱的素人如果也想跨界玩儿音乐,那么门槛儿就有点高了。但随着人工智能技术的快速迭代,现在任何一个人都可以成为“创作型歌手”,即自主创作并…...
ZooKeeper基本知识
1.什么是ZooKeeper ZooKeeper是一个开源的分布式协调服务,它提供了一个高性能、高可靠的分布式协调基础,用于构建分布式系统。 具体来说,ZooKeeper通常用于以下几个方面: 配置管理:分布式系统通常需要集中管理配置信…...
leetcode:138. 随机链表的复制
一、题目: 138. 随机链表的复制 - 力扣(LeetCode) 函数原型: struct Node* copyRandomList(struct Node* head) 二、思路 本题是给出一个单链表,单链表的每个结点还额外有一个随机指针,随机指向其他结点&am…...
SpringBoot 全局异常之参数校验(1)
文章目录 前言背景依赖校验类型@NotBlank、@NotNull和@NotEmpty的区别@Valid和@Validated区别异常处理方式一 @RequestParam全局异常处理(ConstraintViolationException)请求示例方式二 @RequestBody(推荐)全局异常处理(MethodArgumentNotValidException)请求示例方式三(…...
QT windows与linux之间sokcet通信中文乱码问题解决方法
QT windows与linux之间sokcet通信中文乱码问题解决方法 linux发送与接收都转码utf-8: tcpClient ->write( send_msg.toUtf8());//解决乱码,发送转码 接收: QByteArray buffer tcpClient->readAll(); if(!buffer.isEmpty()) { // ui->plain…...
Java实现DXF文件转换成PDF
代码实现 public static void dxfToPdf(){// 加载DXF文件String inputFile "input.dxf";CadImage cadImage (CadImage) Image.load(inputFile);// 设置PDF输出选项PdfOptions pdfOptions new PdfOptions();pdfOptions.setPageWidth(200);pdfOptions.setPageHeigh…...
揭秘Vue中的nextTick:异步更新队列背后的技术原理大揭秘!
🎬 江城开朗的豌豆:个人主页 🔥 个人专栏 :《 VUE 》 《 javaScript 》 📝 个人网站 :《 江城开朗的豌豆🫛 》 ⛺️ 生活的理想,就是为了理想的生活 ! 目录 ⭐ 专栏简介 📘 文章引言 一、N…...
PHP使用文件缓存实现html静态化
<?php // 动态生成的内容 $content "<html><body><h1>time:".date("Y-m-d H:i:s")."</h1></body></html>"; // 静态文件保存路径和文件名 $staticFilePath "file.html"; if(file_exists($s…...
A Gentle Introduction to Graph Neural Networks
A Gentle Introduction to Graph Neural Networks----《图神经网络入门》 图神经网络信息传递积累 图在我们身边随处可见,现实世界中的物体通常是根据它们与其他事物的联系来定义的。一组物体以及它们之间的联系可以很自然地用图来表示。十多年来,研究人…...
详解[ZJCTF 2019]NiZhuanSiWei 1(PHP两种伪协议、PHP反序列化漏洞、PHP强比较)还有那道题有这么经典?
题目环境: <?php $text $_GET["text"]; $file $_GET["file"]; $password $_GET["password"]; if(isset($text)&&(file_get_contents($text,r)"welcome to the zjctf")){echo "<br><h1>&…...
Veo 2提示词效能跃迁实战(工业级Prompt链构建全图谱)
更多请点击: https://codechina.net 第一章:Veo 2提示词编写的核心范式演进 Veo 2作为新一代视频生成模型,其提示词(prompt)工程已从早期的“关键词堆叠”转向结构化、语义分层与意图对齐的复合范式。这一演进并非简…...
BLE四大广播模式详解:可连接/不可连接/定向/周期广播
一、前言在低功耗蓝牙(BLE)开发中,广播(Advertising)是设备发现、连接建立、数据广播、设备重连的核心基石,所有BLE交互流程均始于广播报文的收发。不同于传统经典蓝牙,BLE所有广播行为标准化、…...
QMCDecode终极指南:3步解锁QQ音乐加密格式,实现跨平台音乐自由
QMCDecode终极指南:3步解锁QQ音乐加密格式,实现跨平台音乐自由 【免费下载链接】QMCDecode QQ音乐QMC格式转换为普通格式(qmcflac转flac,qmc0,qmc3转mp3, mflac,mflac0等转flac),仅支持macOS,可自动识别到QQ音乐下载目…...
文件-语言-系统:基础IO-2.0——IO重定向接口,语言层缓冲区,系统级缓冲区。内核级分析!
bit::Shadow✧(≖ ◡ ≖✿ 目录 重定向接口dup2() ">" ">>" "<" 函数原型 输出重定向1和2的使用 文件描述符表 ./a.out运行: "./a.out >"默认重定向是fd 1 合并标准输入输出 缓冲区 什么是缓冲…...
从零到上机:我的第一个Quest 3空间锚点应用是如何跑起来的(附完整Unity工程)
从零到上机:我的第一个Quest 3空间锚点应用是如何跑起来的(附完整Unity工程)第一次戴上Meta Quest 3时,那种虚拟与现实交织的震撼感至今难忘。但作为开发者,更让我着迷的是如何让虚拟物体在真实空间中"记住"…...
PCL 基于强度的双边滤波【2026最新版】
目录 一、算法原理 1、计算步骤 2、算法源码 3、函数解析 4、参考文献 二、代码实现 三、结果展示 四、滤波后未发生变化的原因 五、解决办法 六、结果展示 七、相关链接 本文由CSDN点云侠原创,博客长期更新,本文最近一次更新时间为:2026年5月24日。 一、算法原理 1、计算…...
谷氨酸发酵过程的软测量建模【附模型】
✨ 长期致力于软测量、谷氨酸发酵、动力学模型、支持向量机、高斯过程、变量选择、异常状态研究工作,擅长数据搜集与处理、建模仿真、程序编写、仿真设计。 ✅ 专业定制毕设、代码 ✅ 如需沟通交流,点击《获取方式》 (1)多阶段高斯…...
如何快速集成 react-native-bottom-sheet-behavior:5 分钟搞定 Android 底部弹窗
如何快速集成 react-native-bottom-sheet-behavior:5 分钟搞定 Android 底部弹窗 【免费下载链接】react-native-bottom-sheet-behavior react-native wrapper for android BottomSheetBehavior 项目地址: https://gitcode.com/gh_mirrors/re/react-native-bottom…...
yolo视频识别 车辆速度估计识别 yolo11视频实时速度测量与测速估计
文章目录YOLOv11:视频实时速度测量与测速估计一、YOLOv11概述二、速度测量原理三、距离测量方法四、应用场景五、实践案例以下是关于使用YOLOv11进行视频实时速度测量与测速估计的介绍: YOLOv11:视频实时速度测量与测速估计 随着计算机视觉…...
DeepSeek重复代码识别失效了?5个被90%团队忽略的AST解析盲区及修复清单
更多请点击: https://codechina.net 第一章:DeepSeek代码重复检测失效的真相与影响 DeepSeek-R1 模型在代码理解任务中表现出色,但其内置的代码重复检测机制在特定场景下存在系统性失效。根本原因在于模型对语义等价但语法结构差异显著的代…...
