当前位置: 首页 > news >正文

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

总结

以上就是今天要讲的内容

相关文章:

【跟小嘉学 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简介 变更数据捕获&#xff08;Change Data Capture &#xff0c;简称 CDC&#xff09;&#xff1a;记录 SQL Server 表的插入、更新和删除操作。开启cdc的源表在插入、更新和删除操作时会插入数据到日志表中。cdc通过捕获进程将变更数据捕获到变更表中&#xff0c;通过…...

八、性能测试

八、性能测试 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&#xff0c;采用低功耗RISC-V处理器&#xff0c;内置ITCM SRAM、DTCM SRAM&#xff0c;集成包括MIPI、ISP、CNN、QSPI、UART、I2C、GPIO、百兆以太网等IP&#xff0c;采用SMIC40工艺设计流片。 培训数据包括…...

目标检测后的图像上绘制边界框和标签

效果如图所示&#xff0c;有个遗憾就是CV2在图像上显示中文有点难&#xff0c;也不想用别的了&#xff0c;所以改成了英文&#xff0c;代码在下面了&#xff0c;一定要注意一点&#xff0c;就是标注文件的读取一定要根据自己的实际情况改一下&#xff0c;我的所有图像的标注文件…...

Leetcode: 1. 两数之和 【题解超详细】

前言 有人夜里挑灯看花&#xff0c;有人相爱&#xff0c;有人夜里开车看海&#xff0c;有人leetcode第一题都做不出来。 希望下面的题解可以帮助你们开始 你们的 leetcode 刷题 的 天降之路 题目 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中…...

PHP 通过 Redis 解决并发请求的操作问题

比如PHP收到两个并发的请求A和B&#xff0c;要求只能其中一个请求处理S1操作&#xff0c;另一个请求直接返回失败&#xff0c;可以通过redis去解决&#xff1a; SETNX&#xff08;SET if Not eXists&#xff09;是 Redis 中的一个原子命令&#xff0c;用于设置键-值对&#xf…...

浅谈信息论和信息编码

目录 背景 信息是什么 信息度量 小白鼠实验 哈夫曼编码 密码学 其它应用 背景 克劳德艾尔伍德香农&#xff08;Claude Elwood Shannon&#xff09;出生于 1916 年 美国密歇根州。1936 年毕业于密歇根大学&#xff0c;获得数学和电子工程学士学位。之后&#xff0c;他在麻…...

【测试】笔试02

文章目录 1. 下面不属于软件测试步骤的是2. 关于测试驱动开发&#xff0c;描述错误的是3. 在软件测试中&#xff0c;圈复杂度&#xff08;Cyclomatic complexity&#xff09;&#xff1a;代码逻辑复杂度的度量&#xff0c;提供了被测代码的路径数量。圈复杂度可通过系统控制流图…...

公司内部网段多管控乱,该如何规范跨网文件传输交换?

古往今来&#xff0c;高筑墙一直是有效的防御措施。从边塞长城到护城河外的高高城墙&#xff0c;都是利用隔离地域的形式实现保护安全域的效果。这样一来&#xff0c;城内的安全域可以在遇到危险时受到有效保护。 在企业网络安全防护方面&#xff0c;网络安全域隔离也是网络安全…...

Ceph入门到精通-OSD waring 设置建议

OSD 以下检查表明 OSD 节点存在问题。 警告 1 在 /var/lib/ceph/osd 中找到的多个ceph_fsid值。 这可能意味着您正在托管许多集群的 OSD 此节点或某些 OSD 配置错误以加入 您期望的集群。 2 设置可能会导致数据丢失&#xff0c;因为如果 未达到最小值&#xff0c;Ceph 将不会确…...

软件测试工程师如何快速理解业务?

1. 阅读需求文档和业务资料 仔细阅读与业务相关的文档和资料对于理解业务至关重要。 需求文档通常描述了软件的功能和用户需求&#xff0c;而业务规范则详细说明了业务流程、规则和标准。 仔细阅读这些文档&#xff0c;你可以了解业务的基本概念、要求和流程。 同时&#x…...

【教程】部署apprtc服务中安装google-cloud-cli组件的问题及解决

#0# 前置条件 已经安装完成node&#xff0c;grunt&#xff0c;node 组件和python pip包等。需要安装google-cloud-cli组件。 Ubuntu安装google-cloud-cli组件 apprtc项目运行需要google-cloud-cli前置组件&#xff0c;且运行其中的dev_appserver.py。 根据google官方的关于安…...

C++——shared_ptr:make_shared的用处,与shared_ptr直接构造的区别

shared_ptr shared_ptr继承自__shared_ptr&#xff0c;其中有两个对象&#xff0c;一个是指向资源的指针&#xff0c;一个是控制块&#xff0c;指向一个引用计数对象。控制块中存储了强引用和弱引用的计数&#xff0c;强引用Uses代表shared_ptr对象的引用计数&#xff0c;弱引…...

【网络安全带你练爬虫-100练】第17练:分割字符串

目录 一、目标1&#xff1a;使用函数分割 二、目标2&#xff1a;使用函数模块 三、目标3&#xff1a;使用正则匹配 一、目标1&#xff1a;使用函数分割 目标&#xff1a;x.x.x.x[中国北京 xx云] 方法&#xff1a;split函数replace函数 1、分割&#xff1a;使用split()方法将…...

Unity 之ToolTip的用法

文章目录 在Unity中&#xff0c;ToolTip是一个在编辑器中使用的UI元素&#xff0c;它提供了鼠标悬停在某个对象或控件上时显示的文本信息。ToolTip通常用于向开发人员提供有关对象、字段、控件或菜单项的附加信息&#xff0c;从而帮助他们更好地理解和使用这些元素。 ToolTip通…...

xsschallenge通关(11-15)

level 11 老规矩&#xff0c;先查看源码&#xff0c;做代码审计&#xff1a; <?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” 数据空间&#xff0c;如果有就直接指向它&#xff0c;如果没有就创建然后指向它。s1最终指向的是常量池的空间地址。 s2是先在堆中创建空…...

DBMS Scheduler设置重复间隔

参考文档&#xff1a; Database Administrator’s Guide 29.4.5.2 Using the Scheduler Calendaring Syntax The main way to set how often a job repeats is to set the repeat_interval attribute with a Scheduler calendaring expression. See Also: Oracle Database…...

windows的redis配置sentinel

1、先安装好redis主从&#xff0c;参考我的文章&#xff0c;链接如下 redis主从&#xff08;windows版本&#xff09;_rediswindows版本_veminhe的博客-CSDN博客 2、然后配置sentinel 参考在windows上搭建redis集群&#xff08;Redis-Sentinel&#xff09; 配置时&#xf…...

NetMarvel机器学习促广告收益最大化,加速获客

游戏出海的竞争日益激烈&#xff0c;这并非空穴来风。 从2021年第一季度至2022年第四季度&#xff0c;iOS平台的CPI增长了88%&#xff0c;意味着厂商需要花费近两倍的钱才能获取一个新用户。与此同时数据隐私政策持续收紧&#xff0c;更加提高了营销成本。 在成本高涨的当下&…...

Spring-5.0.x源码下载及本地环境搭建

一、Spring源码下载 从github上下载Spring的源代码 下载地址&#xff1a;https://github.com/spring-projects/spring-framework 访问地址之后&#xff0c;打开Spring的代码页面找到你想下载的版本&#xff0c;如5.0.x&#xff0c;如下图所示&#xff1a; 下载方式一&#x…...

go中的切片

demo1:切片定义的几种方式 package mainimport "fmt"/* 切片定义的几种方式数组和切片区别&#xff1a; 使用数组传参是值传递&#xff0c;而使用切片传参是引用传递 数组定义好长度之后不可修改&#xff0c;而切片可以理解为动态数组&#xff0c;长度可修改*/func …...

C++笔记之单例通过GetInstance传递参数

C笔记之单例通过GetInstance传递参数 code review! 文章目录 C笔记之单例通过GetInstance传递参数例1.普通指针的单例例2.结合智能指针和std::call_once例3.编译不通过的错误例子&#xff0c;在GetInstance内不可以使用std::make_shared来创建对象 例1.普通指针的单例 运行 …...

1688API技术解析,实现获得1688商品详情

要实现获得1688商品详情&#xff0c;你需要使用1688 API。1688 API是阿里巴巴旗下的开放平台&#xff0c;它提供了一套丰富的接口&#xff0c;可以让开发者通过编程的方式获取到1688网站上的商品信息。 首先&#xff0c;你需要在阿里开放平台注册一个账号&#xff0c;并创建一…...

【Java 动态数据统计图】动态X轴二级数据统计图思路Demo(动态,排序,动态数组(重点推荐:难)九(131)

需求&#xff1a; 1.有一组数据集合&#xff0c;数据集合中的数据为动态&#xff1b; 举例如下&#xff1a; [{province陕西省, city西安市}, {province陕西省, city咸阳市}, {province陕西省, city宝鸡市}, {province陕西省, city延安市}, {province陕西省, city汉中市}, {pr…...

C#将text文本中的单双行分开单独保存

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 文本的分割1.设定text文件的名称为02.文本导出 文本的分割 1.设定text文件的名称为0 代码如下&#xff1a; using System; using System.Collections.Generic; us…...

深入理解 Go 语言中的 iota

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