【跟小嘉学 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是不安全的
  • 总结

前言

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是不安全的

总结

以上就是今天要讲的内容

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

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

相关文章

stm32之USART(总结)

串行通信 UART串口内部结构示意图 普中科技的详细介绍 中断知识补充 代码 #ifndef __USART_H #define __USART_H #include "stdio.h" #include "stm32f10x_usart.h" #define USART1_REC_LEN 200 //定义最大接收字节数 200extern u8 USART1_RX_BUF[US…

排序算法:希尔排序

1959 年 7 月&#xff0c;美国辛辛那提大学的数学系博士 Donald Shell 在 《ACM 通讯》上发表了希尔排序算法&#xff0c;成为首批将时间复杂度降到 O(n)以下的算法之一。虽然原始的希尔排序最坏时间复杂度仍然是 O(n) &#xff0c;但经过优化的希尔排序可以达到 O(n1.3)甚至O(…

飞天使-k8s基础组件分析-服务与ingress

文章目录 服务的介绍服务代理服务发现连接集群外服务服务发布无头服务 服务&#xff0c;pod和dns的关系端口转发通过expose 暴露应用服务案例INGRESSMetalLB使用参考文档 服务的介绍 服务的作用是啥&#xff1f; 提供外部调用&#xff0c;保证podip的真实性看看服务解决了什么…

深入理解 Go 语言中的 iota

iota是go语言的常量计数器&#xff0c;只能在常量表达式中使用&#xff0c;iota在const关键字出现时将被重置为0&#xff0c;const中每新增一行常量声明将使iota计数一次&#xff0c;可理解为const语句块中的行索引。它会自动递增&#xff0c;从0开始。 修改步长 尽管默认步长…

淘宝商品数据采集(如何快速获取淘宝商品信息),淘宝API接口申请指南

淘宝作为国内的电商平台&#xff0c;拥有海量的商品信息。对于想要进行淘宝商品数据采集的人来说&#xff0c;如何快速获取淘宝商品信息是一个重要的问题。本文将介绍一些快速获取淘宝商品信息的方法。 1. 使用淘宝开放平台PI 淘宝开放平台提供了多种PI接口&#xff0c;可以通…

五公里场地训练笔记(完整版)

由于考研和口罩等原因&#xff0c;停跑了比较长的时间。中长距离就是这样&#xff0c;修为尽失&#xff0c;大概是要从头开始了&#xff0c;不过还是要乐观的面对&#xff0c;CHEER UP&#xff01; 翻看咕咚软件&#xff0c;以前的PB是21&#xff1a;12&#xff0c;在2017年9月…

Flask 项目结构

前面我们了解了 Flask 框架的特性和一些用法&#xff0c;比如创建一个简单应用、做些页面&#xff0c;以及增加鉴权模块等&#xff0c;如果要将 Flask 用于实际项目开发&#xff0c;还需要了解一下 Flask 项目结构。 Flask 是一个轻量级的 Web 框架&#xff0c;扩展性强&#…

C# Winfrom通过COM接口访问和控制Excel应用程序,将Excel数据导入DataGridView

1.首先要创建xlsx文件 2.在Com中添加引用 3. 添加命名空间 using ApExcel Microsoft.Office.Interop.Excel; --这样起个名字方面后面写 4.样例 //点击操作excelDataTable dt new DataTable();string fileName "D:\desktop\tmp\test.xlsx";ApExcel.Application exA…

uview ui 查看版号

版本查询2种方式 有两种方式可以查询到正在使用的uView的版本&#xff1a; // 通过console.log打印的形式 console.log(uni.$u.config.v);// 可以查阅uView的配置文件得知当前版本号&#xff0c;具体位置为&#xff1a; /uview-ui/libs/config/config.js

在 Google Colab 中微调用于命名实体识别的 BERT 模型

介绍 命名实体识别是自然语言处理(NLP)领域的一项主要任务。它用于检测文本中的实体,以便在下游任务中进一步使用,因为某些文本/单词对于给定上下文比其他文本/单词更具信息性和重要性。这就是 NER 有时被称为信息检索的原因,即从文本中提取相关关键词并将其分类为所需的类…

基于Python+djangoAI 农作物病虫害预警系统智能识别系统设计与实现(源码&教程)

1.背景 随着科技的发展&#xff0c;机器学习技术在各个领域中的应用越来越广泛。在农业领域&#xff0c;机器学习技术的应用有助于提高农作物的产量和质量&#xff0c;降低农业生产的成本。本文针对农作物健康识别问题&#xff0c;提出一种基于机器学习方法的农作健康识别系统&…

Effective C++条款11——在operator=中处理“自我赋值”(构造/析构/赋值运算)

“自我赋值”发生在对象被赋值给自己时: class Widget {}; Widget w; // ... w w; // 赋值给自己 这看起来有点愚蠢&#xff0c;但它合法&#xff0c;所以不要认定客户绝不会那么做。此外赋值动作并不总是那么可被一眼辨识出来&#xff0c;例如: a[i] a[j]; …

在SpringBoot使用MongoDB时出现的bug和解决

在springboot使用MongoDB时出现的bug和解决 在springboot整合MongoDB时,报错 在springboot整合MongoDB时,报错 INFO 67135 — [ main] org.mongodb.driver.connection : Closed connection [connectionId{localValue:2}] to 127.0.0.1:27017 because there was a socket excep…

创建延时队列、springboot配置多个rabbitmq

创建延时队列 queue.file_delay_destroy x-dead-letter-exchange: exchange.file_delay_destroy x-message-ttl: 259200000 259200000为3天,1000为1秒创建普通队列 queue.file_destroy创建普通交换机 exchange.file_delay_destroytype选择fanout 交换机绑定普通队列 (图中…

node没有自动安装npm时,如何手动安装 npm

之前写过一篇使用 nvm 管理 node 版本的文章&#xff0c;node版本管理&#xff08;Windows&#xff09; 有时候&#xff0c;我们使用 nvm 下载 node 时&#xff0c;node 没有自动下载 npm &#xff0c;此时就需要我们自己手动下载 npm 1、下载 npm下载地址&#xff1a;&…

【SpringBoot】第一篇:redis使用

背景&#xff1a; 本文是教初学者如何正确使用和接入redis。 一、引入依赖 <!--redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><depen…

如何延长周末体验感

美好的周末永远都是从周五开始 为了享受周末的美好时光一定要在周五下班前把工作中应该处理的事情处理好&#xff0c;避免突发事件影响后续的计划。 此外过周五晚上开始做让自己感到开心的事情&#xff0c;以此让自己感觉到周末已经开始了。包括单不限于 享受美食 周五晚上是一…

以getPositionList为例,查找接口函数定义及接口数据格式定义

job-app-master/pages/index/index.vue中299行 async getPositionList(type refresh, pulldown false) {this.status 请求中;if (type refresh) {this.query.page 1;} else {this.query.page;}let res await this.$apis.getPositionList(this.query);if (res) {if (type …

Azure sqlserver 更改字符集

前言 我们的Azure SQL Server是在2018年建的&#xff0c;当时还不支持汉字的字符集。然后最近发现因为字符集的缘故&#xff0c;出了bug&#xff0c;要调整字符集。然后就照着sqlserver 排序规则&#xff08;字符集&#xff09;查看与修改 一通修改。 然后神奇的事情来了&…

Vue3.0 新特性以及使用变更总结

Vue3.0 在2020年9月正式发布了&#xff0c;也有许多小伙伴都热情的拥抱Vue3.0。去年年底我们新项目使用Vue3.0来开发&#xff0c;这篇文章就是在使用后的一个总结&#xff0c; 包含Vue3新特性的使用以及一些用法上的变更。 图片.png 为什么要升级Vue3 使用Vue2.x的小伙伴都熟悉…