【跟小嘉学 Rust 编程】十六、无畏并发(Fearless Concurrency)
系列文章目录
【跟小嘉学 Rust 编程】一、Rust 编程基础
【跟小嘉学 Rust 编程】二、Rust 包管理工具使用
【跟小嘉学 Rust 编程】三、Rust 的基本程序概念
【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念
【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据
【跟小嘉学 Rust 编程】六、枚举和模式匹配
【跟小嘉学 Rust 编程】七、使用包(Packages)、单元包(Crates)和模块(Module)来管理项目
【跟小嘉学 Rust 编程】八、常见的集合
【跟小嘉学 Rust 编程】九、错误处理(Error Handling)
【跟小嘉学 Rust 编程】十一、编写自动化测试
【跟小嘉学 Rust 编程】十二、构建一个命令行程序
【跟小嘉学 Rust 编程】十三、函数式语言特性:迭代器和闭包
【跟小嘉学 Rust 编程】十四、关于 Cargo 和 Crates.io
【跟小嘉学 Rust 编程】十五、智能指针(Smart Point)
【跟小嘉学 Rust 编程】十六、无畏并发(Fearless Concurrency)
文章目录
- 系列文章目录
- @[TOC](文章目录)
- 前言
- 一、进程(Process)与线程(Thread)
- 1.1、进程(Process)
- 1.2、线程(Thread)
- 1.2.1、线程
- 1.2.2、多线程可能导致的问题
- 1.2.3、实现线程方式
- 二、多线程开发
- 2.1、使用Thread::spwan 创建线程函数
- 2.2、使用JoinHandle的join 方法等待所有线程执行完毕
- 2.3、使用 move 闭包
- 2.3、使用消息传递
- 2.3.1、使用消息传递
- 2.3.2、通道(Channel)
- 2.3.2.1、使用mpsc::channel 创建 Channel
- 2.3.2.2、发送端 send 方法
- 2.3.2.3、接收端的
- 2.3.2.4、发送多个值和接收等待
- 2.3.2.5、使用 clone 来创建多个发送者
- 2.4、共享状态并发(Shared-State Concurrency)
- 2.4.1、使用 Mutex 每次只允许一个线程来访问数据
- 2.4.1.1、mutex介绍
- 2.4.1.2、mutex的规则
- 2.4.1.3、`Mutex<T>`的API
- 2.4.1.4、在多线程中共享 Mutext 与原子引用计数
- 2.4.1.5、RefCell/RC和Mutex/ARC
- 2.5、通过 Send Trait 和Sync Trait 来扩展并发
- 2.5.1、Send :允许线程间转移所有权
- 2.5.2、Sync :允许线程间转移所有权
- 2.5.3、手动实现 Send 和 Sync是不安全的
- 总结
文章目录
- 系列文章目录
- @[TOC](文章目录)
- 前言
- 一、进程(Process)与线程(Thread)
- 1.1、进程(Process)
- 1.2、线程(Thread)
- 1.2.1、线程
- 1.2.2、多线程可能导致的问题
- 1.2.3、实现线程方式
- 二、多线程开发
- 2.1、使用Thread::spwan 创建线程函数
- 2.2、使用JoinHandle的join 方法等待所有线程执行完毕
- 2.3、使用 move 闭包
- 2.3、使用消息传递
- 2.3.1、使用消息传递
- 2.3.2、通道(Channel)
- 2.3.2.1、使用mpsc::channel 创建 Channel
- 2.3.2.2、发送端 send 方法
- 2.3.2.3、接收端的
- 2.3.2.4、发送多个值和接收等待
- 2.3.2.5、使用 clone 来创建多个发送者
- 2.4、共享状态并发(Shared-State Concurrency)
- 2.4.1、使用 Mutex 每次只允许一个线程来访问数据
- 2.4.1.1、mutex介绍
- 2.4.1.2、mutex的规则
- 2.4.1.3、`Mutex<T>`的API
- 2.4.1.4、在多线程中共享 Mutext 与原子引用计数
- 2.4.1.5、RefCell/RC和Mutex/ARC
- 2.5、通过 Send Trait 和Sync Trait 来扩展并发
- 2.5.1、Send :允许线程间转移所有权
- 2.5.2、Sync :允许线程间转移所有权
- 2.5.3、手动实现 Send 和 Sync是不安全的
- 总结
前言
Rust无畏并发:允许你编写没有细微Bug的代码,并在不引入新Bug 的情况下易于重构。
并发包含如下两种
- Concurrent:程序的不同部分之间独立的执行
- parallel:程序的不同部分同时运行
主要教材参考 《The Rust Programming Language》
一、进程(Process)与线程(Thread)
1.1、进程(Process)
在大部分OS里面,代码运行在进程中,OS同时管理多个进程。
1.2、线程(Thread)
1.2.1、线程
在你程序,各自独立部分可以同时运行,运行这些独立部分的就是线程(Thread)。
多线程运行的好处:
- 提升性能表现
- 增加复杂性:无法保障各线程的执行顺序
1.2.2、多线程可能导致的问题
- 竞争状态:线程以不一致的顺序访问数据或资源;
- 死锁:两个线程彼此等待对方使用完所持有的自由,线程无法继续;
- 只有在某些情况下发生的BUG,很难可靠地复制现象和修复;
1.2.3、实现线程方式
- 通过调用 OS的 API 来创建线程: 1:1 模型,需要较小的运行时
- 语言自己实现的线程(绿色线程):M:N模型,需要更大的运行时
Rust 需要权衡运行时的支持:Rust 标准库仅提供 1:1 模型 的线程
二、多线程开发
2.1、使用Thread::spwan 创建线程函数
use std::thread;
use std::time::Duration;fn main() {thread::spawn(|| {for i in 1..10 {println!("hi number {} from the spawned thread!", i);thread::sleep(Duration::from_millis(1));}});for i in 1..5 {println!("hi number {} from the main thread!", i);thread::sleep(Duration::from_millis(1));}
}
主线程执行完,其他线程还没执行完
2.2、使用JoinHandle的join 方法等待所有线程执行完毕
thread::spawn 函数返回的是 JoinHandle。JoinHandle 持有值的所有权,调用其 join 方法可以等待对应的其他线程的完成。
use std::thread;
use std::time::Duration;fn main() {let handle = thread::spawn(|| {for i in 1..10 {println!("hi number {} from the spawned thread!", i);thread::sleep(Duration::from_millis(1));}});for i in 1..5 {println!("hi number {} from the main thread!", i);thread::sleep(Duration::from_millis(1));}handle.join().unwrap();
}
2.3、使用 move 闭包
move 闭包通常和 thread::spawn 一起使用,它允许你使用其他线程的数据,创建线程的时候把值的所有权从一个线程转移到另外一个线程。
范例:错误示范
use std::thread;fn main() {let v = vec![1, 2, 3];let handle = thread::spawn(|| {println!("Here's a vector: {:?}", v);});handle.join().unwrap();
}
此时存在有如下错误信息
error[E0373]: closure may outlive the current function, but it borrows `v`, which is owned by the current function--> src/main.rs:6:32|
6 | let handle = thread::spawn(|| {| ^^ may outlive borrowed value `v`
7 | println!("Here's a vector: {:?}", v);| - `v` is borrowed here|
note: function requires argument type to outlive `'static`--> src/main.rs:6:18|
6 | let handle = thread::spawn(|| {| __________________^
7 | | println!("Here's a vector: {:?}", v);
8 | | });| |______^
help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword|
6 | let handle = thread::spawn(move || {| ++++
根据提示信息,我们可以使用 move 关键字来修改
use std::thread;fn main() {let v = vec![1, 2, 3];let handle = thread::spawn(move|| {println!("Here's a vector: {:?}", v);});handle.join().unwrap();
}
2.3、使用消息传递
2.3.1、使用消息传递
一种很流行且能够保证安全并发的技术就是:消息传递,线程或者 Actor 通过彼此发送消息(数据)来进行通信。
Go语言名言:不要用共享内存来通信,要用通信来共享内存
Rust 使用标准库中的Channel来
2.3.2、通道(Channel)
Channel 包含发送端和接收端,调用发送端端方法发送数据,接收端会检查和接受到达的数据,如果其中一段被丢弃了,那么 通道就关闭了。
2.3.2.1、使用mpsc::channel 创建 Channel
mpsc(multiple producer single consumer),多个生产者,一个消费者,返回一个 tuple,里面元素分别是发送端和接收端。
use std::sync::mpsc;
use std::thread;fn main() {let (tx, rx) = mpsc::channel();thread::spawn(move || {let val = String::from("hi");tx.send(val).unwrap();});let recevied = rx.recv().unwrap();println!("Got :{}", recevied);
}
2.3.2.2、发送端 send 方法
参数要发送的数据:返回 Result<T,E>,如果有问题,就返回一个错误。 会移交所有权
2.3.2.3、接收端的
1、recv 方法
阻止当前线程执行,直到 Channel 中有值被送来,一旦有值被收到,就返回Result<T,E>,当发送端关闭就会收到一个错误
2、try_recv 方法
不会阻塞,立即返回 Result<T,E>,有数据达到,返回OK,里面包含数据,否则返回错误。
通常使用循环来检查 try_recv 的结果。
2.3.2.4、发送多个值和接收等待
use std::sync::mpsc;
use std::thread;
use std::time::Duration;fn main() {let (tx, rx) = mpsc::channel();thread::spawn(move || {let vals = vec![String::from("hi"),String::from("from"),String::from("the"),String::from("thread"),];for val in vals {tx.send(val).unwrap();thread::sleep(Duration::from_secs(1));}});for received in rx {println!("Got: {}", received);}
}
2.3.2.5、使用 clone 来创建多个发送者
use std::sync::mpsc;
use std::thread;
use std::time::Duration;fn main() {let (tx, rx) = mpsc::channel();let tx1 = tx.clone();thread::spawn(move || {let vals = vec![String::from("hi"),String::from("from"),String::from("the"),String::from("thread"),];for val in vals {tx1.send(val).unwrap();thread::sleep(Duration::from_secs(1));}});thread::spawn(move || {let vals = vec![String::from("more"),String::from("messages"),String::from("for"),String::from("you"),];for val in vals {tx.send(val).unwrap();thread::sleep(Duration::from_secs(1));}});for received in rx {println!("Got: {}", received);}
}
2.4、共享状态并发(Shared-State Concurrency)
Rust 支持通过共享状态实现并发,Channel类似单所有权,一旦值的所有权转移给Channel 就无法使用了。而共享内存并发类似多所有权,多个线程可以同时访问同一块内存。
2.4.1、使用 Mutex 每次只允许一个线程来访问数据
2.4.1.1、mutex介绍
Mutex 是 mutual exclusion(互斥锁),在同一时刻,Mutex 只允许一个线程来访问某些数据,想要访问数据,线程必须获取互斥锁(lock),lock数据结构是 mutex 的一部分,它能跟着谁对数据拥有独占访问权。
mutext 通常被描述为:通过锁来保护它所持有的数据
2.4.1.2、mutex的规则
- 使用数据之前,必须尝试获取锁(lock)
- 使用完 mutext 所保护的数据,必须对数据进行解锁,以便其他线程可以获取锁
2.4.1.3、Mutex<T>的API
使用 Mutex::new(数据) 来创建 Mutex,Mutex 是一个智能指针,访问数据前通过 lock 方法来获取锁:
- 会阻塞当前线程
- lock可能会失败
- 返回的是 MutexGuard(智能指针,实现了Deref 和 Drop)
use std::sync::Mutex;fn main() {let m = Mutex::new(5);{let mut num = m.lock().unwrap();*num = 6;}println!("m = {:?}", m);
}
2.4.1.4、在多线程中共享 Mutext 与原子引用计数
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());
}
该代码报错,因为我们使用了move 移动了所有权,所以其他线程使用会报错。
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());
}
Arc<T> 原子引用计数
2.4.1.5、RefCell/RC和Mutex/ARC
- Mutex 提供了内部可变性和 Cell 家族一样,
- 使用 Refcell 来 改变RC 里面的内容
- 使用 Mutex 来改变 ARC 里面的内容
使用 Mutex 存在死锁风险
2.5、通过 Send Trait 和Sync Trait 来扩展并发
Rust 语言的并发特性比较少,目前讲的并发特性都来自于标准库,无需局限于标准库的并发,可以自己实现并发。
在Rust里面有两个并发的概念
- std::marker::Sync
- std::marker::Send
2.5.1、Send :允许线程间转移所有权
Rust中几乎所有的类型都实现了 Send,实现了 Send Trait 类型可以在线程间转移所有权,但是RC<T> 没有实现 Send,它只适用单线程场景。
任何完全由 Send 类型组成的类型也被标记为 Send。
除了原始指针之外,几乎所有的基础类型都是 Send。
2.5.2、Sync :允许线程间转移所有权
实现 Sync 类型可以完全被多个线程引用,如果 T 是 Sync,那么 &T 就是Send,引用可以被安全的送往另个线程。
基础类型都是 Sync, 完全由 Sync 类型组成的类型也是 Sync,但是 RC不是Sync, RefCell Cell 也不是Sync,Mutex 是 Sync
2.5.3、手动实现 Send 和 Sync是不安全的
总结
以上就是今天要讲的内容
相关文章:
【跟小嘉学 Rust 编程】十六、无畏并发(Fearless Concurrency)
系列文章目录 【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学…...
Android 进阶——图形显示系统之VSync和 Choreographer的创建详解(一)
引言 前一篇文章Android 进阶——图形显示系统之底层图像显示原理小结(一)介绍了关于Android 图形显示系统的基础理论,相信你对于Android的图形显示系统中图形界面渲染刷新机制有了更深的了解,接下来进一步讲解VSync和Choreography的联系和作用。 一、VSync 信号的产生概…...
SQL Server开启变更数据捕获(CDC)
一、CDC简介 变更数据捕获(Change Data Capture ,简称 CDC):记录 SQL Server 表的插入、更新和删除操作。开启cdc的源表在插入、更新和删除操作时会插入数据到日志表中。cdc通过捕获进程将变更数据捕获到变更表中,通过…...
八、性能测试
八、性能测试 8.1 性能测试代码 #include"ConcurrentAlloc.h"// ntimes 一轮申请和释放内存的次数 // rounds 轮次 void BenchmarkMalloc(size_t ntimes, size_t nworks, size_t rounds) {std::vector<std::thread> vthread(nworks);std::atomic<size_t&g…...
景芯SoC 芯片全流程培训
【全网唯一】景芯SoC是一款用于芯片全流程培训的低功耗ISP图像处理SoC,采用低功耗RISC-V处理器,内置ITCM SRAM、DTCM SRAM,集成包括MIPI、ISP、CNN、QSPI、UART、I2C、GPIO、百兆以太网等IP,采用SMIC40工艺设计流片。 培训数据包括…...
目标检测后的图像上绘制边界框和标签
效果如图所示,有个遗憾就是CV2在图像上显示中文有点难,也不想用别的了,所以改成了英文,代码在下面了,一定要注意一点,就是标注文件的读取一定要根据自己的实际情况改一下,我的所有图像的标注文件…...
Leetcode: 1. 两数之和 【题解超详细】
前言 有人夜里挑灯看花,有人相爱,有人夜里开车看海,有人leetcode第一题都做不出来。 希望下面的题解可以帮助你们开始 你们的 leetcode 刷题 的 天降之路 题目 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中…...
PHP 通过 Redis 解决并发请求的操作问题
比如PHP收到两个并发的请求A和B,要求只能其中一个请求处理S1操作,另一个请求直接返回失败,可以通过redis去解决: SETNX(SET if Not eXists)是 Redis 中的一个原子命令,用于设置键-值对…...
浅谈信息论和信息编码
目录 背景 信息是什么 信息度量 小白鼠实验 哈夫曼编码 密码学 其它应用 背景 克劳德艾尔伍德香农(Claude Elwood Shannon)出生于 1916 年 美国密歇根州。1936 年毕业于密歇根大学,获得数学和电子工程学士学位。之后,他在麻…...
【测试】笔试02
文章目录 1. 下面不属于软件测试步骤的是2. 关于测试驱动开发,描述错误的是3. 在软件测试中,圈复杂度(Cyclomatic complexity):代码逻辑复杂度的度量,提供了被测代码的路径数量。圈复杂度可通过系统控制流图…...
公司内部网段多管控乱,该如何规范跨网文件传输交换?
古往今来,高筑墙一直是有效的防御措施。从边塞长城到护城河外的高高城墙,都是利用隔离地域的形式实现保护安全域的效果。这样一来,城内的安全域可以在遇到危险时受到有效保护。 在企业网络安全防护方面,网络安全域隔离也是网络安全…...
Ceph入门到精通-OSD waring 设置建议
OSD 以下检查表明 OSD 节点存在问题。 警告 1 在 /var/lib/ceph/osd 中找到的多个ceph_fsid值。 这可能意味着您正在托管许多集群的 OSD 此节点或某些 OSD 配置错误以加入 您期望的集群。 2 设置可能会导致数据丢失,因为如果 未达到最小值,Ceph 将不会确…...
软件测试工程师如何快速理解业务?
1. 阅读需求文档和业务资料 仔细阅读与业务相关的文档和资料对于理解业务至关重要。 需求文档通常描述了软件的功能和用户需求,而业务规范则详细说明了业务流程、规则和标准。 仔细阅读这些文档,你可以了解业务的基本概念、要求和流程。 同时&#x…...
【教程】部署apprtc服务中安装google-cloud-cli组件的问题及解决
#0# 前置条件 已经安装完成node,grunt,node 组件和python pip包等。需要安装google-cloud-cli组件。 Ubuntu安装google-cloud-cli组件 apprtc项目运行需要google-cloud-cli前置组件,且运行其中的dev_appserver.py。 根据google官方的关于安…...
C++——shared_ptr:make_shared的用处,与shared_ptr直接构造的区别
shared_ptr shared_ptr继承自__shared_ptr,其中有两个对象,一个是指向资源的指针,一个是控制块,指向一个引用计数对象。控制块中存储了强引用和弱引用的计数,强引用Uses代表shared_ptr对象的引用计数,弱引…...
【网络安全带你练爬虫-100练】第17练:分割字符串
目录 一、目标1:使用函数分割 二、目标2:使用函数模块 三、目标3:使用正则匹配 一、目标1:使用函数分割 目标:x.x.x.x[中国北京 xx云] 方法:split函数replace函数 1、分割:使用split()方法将…...
Unity 之ToolTip的用法
文章目录 在Unity中,ToolTip是一个在编辑器中使用的UI元素,它提供了鼠标悬停在某个对象或控件上时显示的文本信息。ToolTip通常用于向开发人员提供有关对象、字段、控件或菜单项的附加信息,从而帮助他们更好地理解和使用这些元素。 ToolTip通…...
xsschallenge通关(11-15)
level 11 老规矩,先查看源码,做代码审计: <?php ini_set("display_errors", 0); $str $_GET["keyword"]; $str00 $_GET["t_sort"]; $str11$_SERVER[HTTP_REFERER]; $str22str_replace(">&quo…...
Kubernetes技术--k8s核心技术集群的安全机制RBAC
1.引入 我们在访问k8s的集群的时候,需要经过一下几个步骤: -a:认证 -1).传输安全:对外是不暴露端口:8080,只能够在内部访问,对外使用的是6443端口。 -2).客户端认证的常用几种方式: -https证书 基于ca证书 -https token认证 通过token识别用户 -https <...
【JavaSE】String类
两种创建String对象的区别 String s1 "hello"; String s2 new String("hello");s1是先查看常量池是否有 “hello” 数据空间,如果有就直接指向它,如果没有就创建然后指向它。s1最终指向的是常量池的空间地址。 s2是先在堆中创建空…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)
引言 在人工智能飞速发展的今天,大语言模型(Large Language Models, LLMs)已成为技术领域的焦点。从智能写作到代码生成,LLM 的应用场景不断扩展,深刻改变了我们的工作和生活方式。然而,理解这些模型的内部…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能
1. 开发环境准备 安装DevEco Studio 3.1: 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK 项目配置: // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...
LabVIEW双光子成像系统技术
双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制,展现出显著的技术优势: 深层组织穿透能力:适用于活体组织深度成像 高分辨率观测性能:满足微观结构的精细研究需求 低光毒性特点:减少对样本的损伤…...
Golang——7、包与接口详解
包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...
