Rust编程中的共享状态并发执行

1.共享状态并发

虽然消息传递是一个很好的处理并发的方式,但并不是唯一一个。另一种方式是让多个线程拥有相同的共享数据。在学习Go语言编程过程中大家应该听到过一句口号:"不要通过共享内存来通讯"。

在某种程度上,任何编程语言中的信道都类似于单所有权,因为一旦将一个值传送到信道中,将无法再使用这个值。共享内存类似于多所有权:多个线程可以同时访问相同的内存位置。第十五章介绍了智能指针如何使得多所有权成为可能,然而这会增加额外的复杂性,因为需要以某种方式管理这些不同的所有者。Rust 的类型系统和所有权规则极大的协助了正确地管理这些所有权。作为一个例子,让我们看看互斥器,一个更为常见的共享内存并发原语。

互斥器mutex)是 mutual exclusion 的缩写,也就是说,任意时刻,其只允许一个线程访问某些数据。为了访问互斥器中的数据,线程首先需要通过获取互斥器的 lock)来表明其希望访问数据。锁是一个作为互斥器一部分的数据结构,它记录谁有数据的排他访问权。因此,我们描述互斥器为通过锁系统 保护guarding)其数据。

互斥器以难以使用著称,因为你不得不记住:

  1. 在使用数据之前尝试获取锁。

  2. 处理完被互斥器所保护的数据之后,必须解锁数据,这样其他线程才能够获取锁。

作为一个现实中互斥器的例子,想象一下在某个会议的一次小组座谈会中,只有一个麦克风。如果一位成员要发言,他必须请求或表示希望使用麦克风。一旦得到了麦克风,他可以畅所欲言,然后将麦克风交给下一位希望讲话的成员。如果一位成员结束发言后忘记将麦克风交还,其他人将无法发言。如果对共享麦克风的管理出现了问题,座谈会将无法如期进行!

正确的管理互斥器异常复杂,这也是许多人之所以热衷于信道的原因。然而,在 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 变量来存放内含 i32Mutex<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>和线程安全的内容。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/174293.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Django(五、视图层)

文章目录 一、视图层1.视图函数返回值的问题2.三板斧的使用结论&#xff1a;在视图文件中写视图函数的时候不能没有返回值&#xff0c;默认返回的是None&#xff0c;但是页面上会报错&#xff0c;用来处理请求的视图函数都必须返回httpResponse对象。 二、JsonReponse序列化类的…

力扣 232. 用栈实现队列(C语言实现)

目录 1.解题思路2.代码实现 1.解题思路 利用两个栈&#xff0c;设栈s1为入栈,s2为出栈&#xff0c;则当s2为空时&#xff0c;出队列只能将s1的后N-1项挪到s2后剩下的就为出栈的项&#xff0c;但如果s2不为空那就说明此时s2栈中的元素就已经是按照队列的顺序排好了&#xff0c;…

HTML5学习系列之主结构

HTML5学习系列之主结构 前言HTML5主结构定义页眉定义导航定义主要区域定义文章块定义区块定义附栏定义页脚 具体使用总结 前言 学习记录 HTML5主结构 定义页眉 head表示页眉&#xff0c;用来表示标题栏&#xff0c;引导和导航作用的结构元素。 <header role"banner…

解析JSON字符串:属性值为null的时候不被序列化

如果希望属性值为null及不序列化&#xff0c;只序列化不为null的值。 1、测试代码 配置代码&#xff1a; mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 或者通过注解JsonInclude(JsonInclude.Include.NON_NULL) //常见问题2&#xff1a;属性为null&a…

8 历史服务器配置

为了查看程序的历史运行情况&#xff0c;需要配置一下历史服务器 1、配置mapred-site.xml vim mapred-site.xml在该文件里面增加如下配置 //原先的配置不用删除 <!-- 历史服务器端地址 --> <property><name>mapreduce.jobhistory.address</name><…

缺陷预测(一)——论文复现

运行CGCN文件 问题一&#xff1a;CNN输入维度的问题出现的问题解决问题原因 问题二&#xff1a;mix时&#xff0c;输入的train_in和train_gen.inputs数据格式不一致出现的问题解决问题 最终结果 问题一&#xff1a;CNN输入维度的问题 出现的问题 数据集改好之后&#xff0c;出…

D. Jumping on Walls bfs

Problem - 199D - Codeforces 题目大意&#xff1a;有一个两个垂直的平行墙壁组成的一个峡谷。一个人初始是在左边墙壁第一层。在每个墙壁上有些障碍点&#xff0c;用X表示&#xff0c;这些障碍点不能被到达。&#xff0c;他可以执行以下三个操作&#xff1a; 向当前墙壁往上…

Win10系统把D盘空间分给C盘的方法

在Win10系统中&#xff0c;用户发现C盘的空间太少了&#xff0c;导致电脑运作出现了卡顿的问题&#xff0c;所以想把D盘的空间分给C盘&#xff0c;但不清楚具体的操作步骤。接下来小编给大家介绍两种简单的操作方法&#xff0c;帮助大家轻松将Win10系统D盘的空间分给C盘&#x…

JavaScript_表单校验用户名和密码

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>注册页面</title><style>*{margin: 0px;padding: 0px;box-sizing: border-box;}body{background: url("img/register_bg.png") …

邻接矩阵储存图实现深度优先遍历(C++)

目录 基本要求&#xff1a; 图的结构体&#xff1a; 图的构造&#xff1a; 图的深度优先&#xff08;DFS&#xff09;&#xff1a; 图的打印输出&#xff1a; 完整代码&#xff1a; 测试数据&#xff1a; 运行结果&#xff1a; 通过给出的图的顶点和边的信息&#xff0c…

mindspore mindyolo目标检测华为昇腾上推理使用、训练;华为OBS文件传输使用

参考&#xff1a; https://github.com/mindspore-lab/mindyolo 使用案例&#xff1a; https://github.com/mindspore-lab/mindyolo/blob/master/GETTING_STARTED.md 安装&#xff1a; pip install mindyolo特别注意opencv-python、opencv-python-headless版本问题&#xff0…

js运算,笔试踩坑知识点

文章目录 前端面试系列运算符记住口诀先计算 后 赋值赋值从右向左 和 - -计算从左向右括号里的加减优先于括号外的乘除交换俩数的值答案 前端面试系列 js运算 笔试踩坑知识点 前端js面试题 &#xff08;三&#xff09; 前端js面试题&#xff08;二&#xff09; 前端js面试题 (…