2311rust无畏并发.
原文
Rust
无畏并发
Rust
是为了解决两个麻烦
问题:
1,如何安全
系统编程
2,如何无畏并发
最初,这些问题似乎是无关
的,但令惊讶的是,方法竟然是相同的:使Rust
安全的相同工具
也可帮助解决并发问题
.
内存安全和并发
错误,一般认为是代码
在不应访问数据时访问数据.Rust
依靠所有权
为你静态检查
.
对内存安全
,即可在无垃集
时编程,也不必担心段错误
,因为Rust
会发现你的错误.
对并发性
,即可从(传递消息,共享状态,无锁,纯函数式
)中选择,而Rust
帮助你避免常见
的陷阱.
以下是Rust
中的并发性:
1,通道
转移了发送消息
的所有权,因此可从一个线程发送指针
到另一个线程,而不必担心线程
竞争.Rust
通道强制隔离线程
.这里
2,锁
知道它保护了哪些数据
,且Rust
保证,只有在持有锁
时,才能访问数据.而不会共享状态
.在Rust
中强制"锁定
数据,而不是代码
".
3,在多线程之间,每种数据类型
都知道它是否可安全
发送或访问,且Rust
强制,即使对无锁
数据结构,也无数据竞争
.线安
不仅是文档;也是规则
.
4,甚至可在线程
间共享栈帧
这里,Rust
静态地确保,在其他
线程使用它们时,这些帧
仍活跃
.即使是最大胆
的共享形式,在Rust
中也能保证安全.
这些好处都来自Rust
的所有权模型
,事实上,锁,通道,无锁数据结构
等都是在库中
而不是核心语言
中定义的.
即Rust
的并发
方法是开放
的:新库
可带有新的范式
并抓新的错误
,只需添加使用Rust
所有权功能的API
.
背景:所有权
在Rust
中,每个值
都有个"物主域
",传递或返回
值表明从旧所有权
转移("移动"
)到新域
.在结束域
时,此时自动析构
仍拥有的值.
看看简单
示例.假设创建一个向量
并推送一些元素
到它上面:
fn make_vec() {let mut vec = Vec::new();//归`make_vec`的域所有vec.push(0);vec.push(1);//域结束,析构`"vec"`
}
创建值
的域最初也拥有
它.此时,make_vec
的主体是vec
的物主域.物主可用vec
干活.
在域结束时,仍归域所有vec
,因此会自动释放
.
如果返回或传递
向量,会更有趣:
fn make_vec() -> Vec<i32> {let mut vec = Vec::new();vec.push(0);vec.push(1);vec //转让`所有权`给调用者
}
fn print_vec(vec: Vec<i32>) {//`"vec"`参数是此域的一部分,因此归`"print_vec"`所有for i in vec.iter() {println!("{}", i)}//现在,释放`"vec"`
}
fn use_vec() {let vec = make_vec(); //取向量所有权,print_vec(vec); //传递所有权给`"print_vec"`
}
现在,在make_vec
域结束前,vec
返回它来出域
;不会析构它.然后,像use_vec
此调用者获得向量
所有权.
另一方面,print_vec
函数带vec
参数,由其调用者把向量的所有权
转移给它.因为print_vec
不会进一步转移
所有权,因此在其域
结束时,就析构向量
.
一旦放弃所有权
,就不能再使用该值
.如,请考虑以下use_vec
变体:
fn use_vec() {let vec = make_vec(); //取`VectorPass`所有权print_vec(vec); //传递所有权给`"print_vec"`,for i in vec.iter() { //继续使用`"vec"`println!("{}", i * 2)}
}
编译器说不再可用vec
;已转移所有权
.这非常好,因此时已释放了向量
!避免了灾难.
借贷
目前,并不满意,因为无意
让print_vec
析构向量.真正想要的是临时授予print_vec
访问向量,然后继续使用向量
.
这就要靠借贷
了.如果有权访问Rust
中的某个值,可把该权限
借给调用
函数.Rust
检查这些生命期
不会超过被借
对象.
要借用
一个值,可用&
符号引用
它(一个指针):
fn print_vec(vec: &Vec<i32>) {//`"vec"`参数是`此域`借用的for i in vec.iter() {println!("{}", i)}//现在,借期结束了
}
fn use_vec() {let vec = make_vec(); //取向量的所有权print_vec(&vec); //借出`"print_vec"`权限for i in vec.iter() { //继续使用`"vec"`println!("{}", i * 2)}//在此析构`VEC`
}
现在print_vec
接受向量
引用,use_vec
通过编写&vec
来借出向量.因为是临时借
的,use_vec
保留了向量
所有权;
可在调用print_vec
返回后继续
使用它.
每个引用
在有限域
内有效,编译器
自动确定
该域.有两种引用
形式:
1,不变引用&T
,允许共享
但禁止
改变.可同时有多个
对同一值的&T
引用,但当这些引用
活动时,不能更改该值
.
2,可变引用&mut T
,允许改变
但不共享
.如果存在
对某个值的&mut T
引用,则此时
不能有其他活动引用
,但可更改该值
.
Rust
在编译时检查这些规则;借用
没有运行时成本.
为什么有两类
引用?考虑此函数:
fn push_all(from: &Vec<i32>, to: &mut Vec<i32>) {for i in from.iter() {to.push(*i);}
}
此函数
遍历向量的每个元素
,把它推送
到另一个向量
上.迭代器
在当前和最终位置保持向量
指针,挨个前进.
如果用相同
向量,为两个
参数调用
此函数怎么办?
push_all(&vec, &mut vec)
这将是一场灾难
!推送
元素到向量
上时,它偶尔要调整
,分配
大量新内存并复制进元素
.迭代器
会剩下旧内存
指针的悬挂
指针,导致内存
不安全(段错误
则更糟).
幸好,Rust
确保每当可变
借用活动时,其他借用
都不会活动,从而产生
以下消息:
错误:不能按可变借用"vec"
,因为它也按不变
借用.
push_all(&vec, &mut vec);^~~
传递消息
并发编程
有多种风格,特别简单
方式是线程或参与者
相互发送消息
来通信的传递消息
.
不通过共享内存
交流;相反,通过交流
来共享
内存.
Rust
所有权使得很容易检查
规则.考虑以下通道API
(Rust
标准库中的通道略有不同):
fn send<T: Send>(chan: &Channel<T>, t: T);
fn recv<T: Send>(chan: &Channel<T>) -> T;
通道
在它们传输的数据类型(API
的<T:Send>
部分)上是通用
的.Send
部分表明T
必须是安全
的,可在线程
之间发送;
Vec<i32>
是Send
.
与Rust
中一样,传递T
给send
函数表明转移
它的所有权.这一事实
有深远
影响:即,下面代码
生成编译器错误
.
//假设`chan:Channel<Vec<i32>>`
let mut vec = Vec::new();
//做一些计算
send(&chan, vec);
print_vec(&vec);
在此,线程
创建了一个向量
,并发送
它到另一个线程
,然后继续
使用它.当该线程
继续运行时,接收向量
线程可能会更改它,因此调用print_vec
,可能会导致竞争
,因此,导致释放后使用
错误.
相反,在调用print_vec
时,Rust
编译器会生成
错误消息:
错误:使用移动
的"vec"
值.
避免了灾难.
锁
锁,被动
的共享
状态来通信的方式.
共享状态
并发有个缺点
.很容易
忘记取锁,或在错误
时间改变
错误数据,导致灾难
.
Rust
的观点是:
然而,共享状态
并发是基本
编程风格,系统代码,最大性能及实现
其他并发风格都需要它.
问题与意外
共享状态有关.
无论使用有锁
还是无锁
技术,Rust
旨在为你提供直接征服共享状态
并发的工具.
在Rust
中,因为所有权
,线程会自动相互"隔离"
.无论是拥有
数据,还是可变借用
数据,仅当线程
有可变权限
时,才会写入
.
总之,保证
该线程是当时唯一
有权限的线程.
请记住,不能同时有可变
借用与其他
借用.锁通过运行时
同步提供相同
的保证("互斥"
).这导致
直接勾挂到Rust
所有权系统的锁API
.
如下是简化版本:
//创建新的互斥锁
fn mutex<T: Send>(t: T) -> Mutex<T>;
//取锁
fn lock<T: Send>(mutex: &Mutex<T>) -> MutexGuard<T>;
//访问受锁保护的数据
fn access<T: Send>(guard: &mut MutexGuard<T>) -> &mut T;
此锁API
的不寻常点.
1,首先,在锁保护
数据T类型
上,互斥类型
是通用
的.创建
互斥锁时,转移该数据
所有权到互斥锁
中,立即放弃
了所有权.(在首次创建锁
时解锁).
2,稍后,你可锁(lock)
以阻止
线程,直到获得锁
.在析构MutexGuard
时自动释放
锁;没有单独的解锁(unlock)
函数.
3,只能通过访问(access)
函数访问
锁,该函数把守卫
的可变借用
转换为数据
的可变借用(短期借用
):
fn use_lock(mutex: &Mutex<Vec<i32>>) {//获得锁,拥有警卫;在域的其余部分持有锁let mut guard = lock(mutex);//通过可变借用`Guard`来访问数据let vec = access(&mut guard);//`vec`的类型为`"&mut Vec<i32>"`vec.push(3);//析构`"守卫"`时,会自动在此处释放锁
}
两个
关键要素:
1,访问(access)
返回的可变引用
不能超过
比它借用的MutexGuard
.
2,仅当析构MutexGuard
时,才会释放
锁.
结果是Rust
强制保证锁规则
:除非持有
锁,否则禁止访问
受锁保护数据
.否则生成
编译器错误.如,考虑以下有缺陷的"重构":
fn use_lock(mutex: &Mutex<Vec<i32>>) {let vec = {//取锁let mut guard = lock(mutex);//试返回借用数据access(&mut guard)//在此析构`守卫`,释放了锁};//试访问锁外数据.vec.push(3);
}
Rust
生成错误
来说明问题:
错误:"guard"
的生命期不够长
access(&mut guard)^~~~~
避免了灾难.
线安和"发送"
一般区分
某些数据类型为"线安"
,而其他数据类型则不是
.线安
数据结构内部有足够同步
,以便可同时安全地使用多线程
.
如,Rust
附带了两个来引用计数的"灵针":
1,Rc<T>
通过正常读/写
提供引用计数
.它不是线安
的.
2,Arc<T>
通过原子
操作提供引用计数
.它是线安
的.
Arc
使用的硬件
原子操作比Rc
使用的普通操作更贵,因此使用Rc
而不是Arc
是有利的.另一方面,重点,永远不要从一个线程迁移Rc<T>
到另一个线程,因为会导致破坏
引用计数的竞争
.
在Rust
中,世界分为两个
数据类型:一个是Send
,即可安全
地从一个线程移动
到另一个线程,其余是!Send(不安全)
.
如果某个
类型的所有组件
都是Send
,则该类型
也是Send
,它涵盖
了大多数类型.但是,某些基本类型
不是线安的,因此也可按Send
显式标记Arc
等类型,对编译器
说:相信我;已在此验证
了必要的同步.
当然,Arc
是Send
,而Rc
不是.
可见,通道
和互斥API
仅适合发送(Send)数据
.因为它们是跨越线程边界的数据
点,因此它们也是Send
强制点.
综上,Rust
可自信地获得Rc
和其他线程不安全类型的好处,因为,如果不小心试发送
一个线程到另一个线程,Rust
编译器会说:
无法安全地在线程之间发送"Rc<Vec<i32>>"
.
这避免了灾难.
共享栈:"scoped"
注意:这里提到的API
是一个旧的API
,已从标准库中移出.你可在横梁(scope()
文档)和scoped_threadpool(scoped()
文档)中找到等效的函数.
目前,所有模式
都涉及在堆
上创建,在线程
间共享
的数据结构.但是,如果想启动一些线程
来利用栈帧
中的数据,则可能会很危险:
fn parent() {let mut vec = Vec::new();//填充向量thread::spawn(|| {print_vec(&vec)})
}
子线程
接受vec
引用,而vec
又保留
在父线程的栈帧中.父线程
退出时,会弹出栈帧
,但子线程并不知道.哎呀!
为了排除该内存不安全,Rust
的基本线程生成API
如下:
fn spawn<F>(f: F) where F: 'static, ...
"静态约束
"即,指在闭包
中禁止
借用数据.即像上面此parent
函数会生成错误:
错误:"vec"
的生命期不够长.
基本上抓住了弹出父栈帧
的可能性.避免了灾难.
还有另一个
方法可保证安全性:直到子线程完成
,确保父栈帧
保持原位.这是分叉连接
编程的模式,一般用于分而治之
的并行算法.
Rust
通过提供线程生成的"域"
变体来支持它:
fn scoped<'a, F>(f: F) -> JoinGuard<'a> where F: 'a, ...
与上面的spawn
接口有两个主要
区别:
1,使用'a
参数,而不是'static
.
2,JoinGuard
返回值.即,JoinGuard
通过在其析构器
中隐式连接
(如果尚未显式)来确保父线程
加入(等待)其子线程
.
在JoinGuard
中包含'a
可确保JoinGuard
无法逃脱闭包
借用的数据的域.即,Rust
保证在弹出
子线程可能访问的栈帧
前,父线程
等待子线程
完成.
因此,调整之前示例,可如下修复
错误并满足编译器
:
fn parent() {let mut vec = Vec::new();//填充向量let guard = thread::scoped(|| {print_vec(&vec)});//在此析构`守卫`,隐式合并
}
因此,在Rust
中,可自由地把栈数据
借用到子线程
中,编译器
会确保
检查是否有足够
同步.
数据竞争
Rust
使用所有权和借用
来保证:
1,内存安全,无垃集.
2,无并发数据竞争
.
相关文章:

2311rust无畏并发.
原文 Rust无畏并发 Rust是为了解决两个麻烦问题: 1,如何安全系统编程 2,如何无畏并发 最初,这些问题似乎是无关的,但令惊讶的是,方法竟然是相同的:使Rust安全的相同工具也可帮助解决并发问题. 内存安全和并发错误,一般认为是代码在不应访问数据时访问数据.Rust依靠所有权为…...

阿里云中的云服务器的ubuntu中的vim没有显示行号
没有行号: 在终端输入命令: vim ~/.vimrc set nu...

Golang 在 Mac、Linux、Windows 下如何交叉编译
Golang 支持交叉编译,在一个平台上生成另一个平台的可执行程序。 GOOS:目标平台的操作系统(darwin、freebsd、linux、windows) GOARCH:目标平台的体系架构(386、amd64、arm) 具体组合…...

如何写好一篇学术论文
目录 前言 1.标题和摘要 1.1标题 1.2摘要及关键词 1.2.1摘要 1.2.2关键词 2.正文 2.1引言 2.2问题建模 2.3研究方法及分析 2.4仿真(伪代码) 2.5实验结果及分析 2.6 总结 2.7延深 2.7.1图片处理 2.7.2审稿回复 2.7.3如何避免拒稿 2.7.4写…...

kubernetes资源监控
目录 一、资源限制 1、limitrange 2、ResourceQuota 二、metrics-server 三、图形化监控和代码行监控 1、dashboard 2、k9s 四、hpa 一、资源限制 Kubernetes采用request和limit两种限制类型来对资源进行分配。request(资源需求):即运行Pod的节点必须满足运…...

Bitget Wallet:使用 Base 链购买 ETH 的简明教程
Base 链是一种 Layer 2(L2)公链,它可以为用户提供以太坊(ETH)代币,而 Bitget Wallet 是一款多功能加密货币钱包,支持 Base 链以及其他主要区块链。...

PostgreSQL简介及安装步骤
PostgreSQL简介 PostgreSQL是一款开源的关系型数据库管理系统,具有强大的扩展性、高度的可定制性和可靠的稳定性,因此在企业级应用和开发领域中得到了广泛的应用。本文将介绍PostgreSQL的基本概念以及在各种操作系统上的安装步骤。 安装步骤 1. Window…...

《安富莱嵌入式周报》第326期:航空航天级CANopen协议栈,开源USB PD电源和功耗分析,开源EtherCAT伺服驱动板,时序绘制软件,现代机器人设计
周报汇总地址:嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 更新一期视频教程: BSP视频教程第28期:CANopen协议栈专题,CANopen主从机组网实战&a…...

[Kettle] Excel输入
Excel文件采用表格的形式,数据显示直观,操作方便 Excel文件采用工作表存储数据,一个文件有多张不同名称的工作表,分别存放相同字段或不同字段的数据 数据源 物理成绩(Kettle数据集2).xls https://download.csdn.net/download/H…...

vue3+ts 项目遇到的问题和bug
1.router中使用pinia报错 pinia.mjs:1709 Uncaught Error: [🍍]: "getActivePinia()" was called but there was no active Pinia. Are you trying to use a store before calling "app.use(pinia)"? See https://pinia.vuejs.org/core-concep…...

【Linux】补充:进程管理之手动控制进程,以及计划任务
目录 一、手动启动进程 1、理解前台启动与后台启动 2、如何完成前台启动后台启动的切换 3、完成并行执行多个任务 4、结束进程 1、kill 2、killall 2、pkill 二、计划任务 1、at一次性计划任务 2、实操 2、周期性计划任务 1、关于设置周期性任务的配置文件以及格式…...

听说,工作能力强的项目经理都有这几个特征
大家好,我是老原。 很多项目经理每天忙忙碌碌,但是一看结果,团队业绩没有完成、人才没有培养起来、自己的管理水平和个人领导力也没有得到提升。 明明付出了很多时间和精力,结果却只收获了团队的抱怨,以及老板对你管…...

合并两个有序链表OJ
合并两个有序链表OJ 文章目录 合并两个有序链表OJ一、题目及要求二、思路分析三、代码实现 一、题目及要求 二、思路分析 其次,题目里说了新链表是通过拼接原来的结点形成的,所以说我们不需要开辟新的空间。 三、代码实现 if (list1 NULL) {return li…...

2023NOIP A层联测27 A.kotori
2023NOIP A层联测27 A.kotori 文章目录 2023NOIP A层联测27 A.kotori题目大意思路code 题目大意 琴里的飞船中有 n n n 个人,其中有 n − 1 n - 1 n−1 个通道,所以飞船的内部是一个树形结构。每个人从 1 − n 1-n 1−n 编号,编号越小代表…...

循环生成el-descriptions-item
0 后端返回数据格式 {"msg": "操作成功","code": 200,"data": {"id": 42,"contactInfo": [{"contactPerson": "张三","contactPhone": "13688888888"},{"contactP…...

【原创】java+swing+mysql爱心捐赠管理系统设计与实现
摘要: 爱心捐赠管理系统旨在管理和优化捐赠过程,提高效率,增强透明度,并鼓励更多的个人和企业参与公益捐赠,用户可以捐款或者捐物。本系统采用javaswing界面可视化技术,数据库使用mysql。 功能分析&#…...

【小技巧】WPS统计纯汉字(不计标点符号)
【小技巧】WPS统计纯汉字(不计标点符号) 首先,CtrlF打开查找页面: 选择“高级搜索”,然后勾选“使用通配符”,然后在“查找内容”后面输入:[一-﨩]。注意:一定要带“[]”和“-”且…...

【押题】24考研押题
数二选手来押24数一考研大题 1.大题必有级数。级数出在压轴题,考级数敛散性与数列极限的结合 2.数一倒数第二题65%考画不出图的三重积分,参考19年出法;35%考第一类曲面积分与空间解析几何的结合。大题不会考第二类线面积分 3.概率大题会考参数…...

前端设计模式
前端设计模式 🎨 设计模式是在软件开发中,针对常见问题的解决方案的经验总结。在前端开发中,设计模式可以帮助我们组织和管理代码,提高代码的可维护性和可扩展性。下面列举一些常见的前端设计模式: 1. 单例模式 (Sin…...

Tomcat的类加载器
详情可以参考:https://tomcat.apache.org/tomcat-10.1-doc/class-loader-howto.html 简要说明 Tomcat安装了多种类加载器,以便容器的不同部分、容器中的应用访问能够不同的类和资源。 在Java环境中,类加载器被组织为父-子树的形式。通常情况…...

汽车驾驶智能座舱太阳光模拟器老化试验
一、太阳光模拟器老化试验目的 太阳光模拟器氙光灯老化试验是一种常用的材料老化测试方法,通过模拟自然光照条件下的老化过程,评估材料的耐光性能和耐候性能其主要目的有: 1.评估材料在长时间暴露于自然光照条件下的耐久性能: 2.比较不同材料的耐光性…...

记录一次校园CTF--wp
一.第一题简单nc 这题直接nc 地址端口即可得到flags没有套路 二.第二题pwn:ezstack 这是一题栈溢出题目,查看保护: 没有开启PIE,运行下查看效果: 题目是一个文字购物游戏。 接着扔进IDA中分析: 在主函数中我们找到…...

基于减法平均算法的无人机航迹规划-附代码
基于减法平均算法的无人机航迹规划 文章目录 基于减法平均算法的无人机航迹规划1.减法平均搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要:本文主要介绍利用减法平均算法来优化无人机航迹规划。 …...

C语言--每日五道选择题--Day4
第一题 1、如果 x2014 ,下面函数的返回值是( ) int fun(unsigned int x) {int n 0;while(x 1){n;x x | (x 1);}return n; } A: 20 B: 21 C: 23 D: 25 答案及解析 C 这个函数的作用是对整型中0的个数进行统计 x x | (x1) 的作用是每次…...

OpenCV图片验证码识别与滑块验证码识别
目录 图片验证码识别: 一、百度OCR文字识别云服务 二、维普网获取图片验证码 三、维普网opencvocr识别验证码 四、维普网selenium登录并获取数据 滑块验证码: 五、猎聘网获取滑块验证码 六、猎聘网opencv计算滑动距离 七、猎聘网selenium模拟滑…...

网络安全深入学习第八课——代理与端口转发
文章目录 一、什么是代理二、正向代理三、反向代理四、正向和反向代理模拟复现 一、什么是代理 代理服务器英文全称是Proxy Server,其功能就是代理网络用户去取得网络信息。 形象的说:它是网络信息的中转站。在一般情况下,我们使用网络浏览…...

11月7日,每日信息差
今天是2023年11月07日,以下是为您准备的17条信息差 第一、五粮液否认内部讨论提价传闻 第二、雷军证实小米14销量已超百万台 第三、支付宝生活号全面开放UGC入口。据了解,今年以来,支付宝生活号陆续上线了创作者中心、热点榜单等多个内容产…...

sql异常Encountered unexpected token BINARY
1.出现错误 2023-11-06 10:48:19.604 [http-nio-8091-exec-3] WARN c.b.m.e.p.i.PaginationInnerInterceptor - [autoCountSql,343] - [e322891e-de87-4d98-8456-f6448d3c165e] - optimize this sql to a count sql has exception, sql:"selects.id,s.command,s.catego…...

P1131 [ZJOI2007] 时态同步
Portal. 先找出树上以 S S S 为起点最长的一条链,然后让其他链的长度都和该链对齐即可。 维护每个结点 x x x 的子树最长链 d max ( x ) d_{\max}(x) dmax(x),则每次 DFS 求出最长链之后调整对齐的代价为 d max ( x ) − ( d max ( s o …...

springboot(ssm 旅游管理系统 旅游规划平台 Java(codeLW)
springboot(ssm 旅游管理系统 旅游规划平台 Java(code&LW) 开发语言:Java 框架:ssm/springboot vue JDK版本:JDK1.8(或11) 服务器:tomcat 数据库:mysql 5.7(或8.0ÿ…...