Rust编程中的线程间通信

1.消息传递

为了实现消息传递并发,Rust 标准库提供了一个 信道channel)实现。信道是一个通用编程概念,表示数据从一个线程发送到另一个线程。

可以将编程中的信道想象为一个水流的渠道,比如河流或小溪。如果你将诸如橡皮鸭或小船之类的东西放入其中,它们会顺流而下到达下游。编程中的信息渠道(信道)有两部分组成,一个发送者(transmitter)和一个接收者(receiver)。发送者位于上游位置,在这里可以将橡皮鸭放入河中,接收者则位于下游,橡皮鸭最终会漂流至此。代码中的一部分调用发送者的方法以及希望发送的数据,另一部分则检查接收端收到的消息。当发送者或接收者任一被丢弃时可以认为信道被 关闭closed)了。

下面的代码我们创建了一个信道但没有做任何事。注意这还不能编译,因为 Rust 不知道想要在信道中发送什么类型:

use std::sync::mpsc;
​
fn main() {let (tx, rx) = mpsc::channel();
}

这里使用 mpsc::channel 函数创建一个新的信道;mpsc多个生产者,单个消费者multiple producer, single consumer)的缩写。简而言之,Rust 标准库实现信道的方式意味着一个信道可以有多个产生值的 发送sending)端,但只能有一个消费这些值的 接收receiving)端。想象一下多条小河小溪最终汇聚成大河:所有通过这些小河发出的东西最后都会来到下游的大河。目前我们以单个生产者开始,但是当示例可以工作后会增加多个生产者。

mpsc::channel 函数返回一个元组:第一个元素是发送端 -- 发送者,而第二个元素是接收端 -- 接收者。由于历史原因,txrx 通常作为 发送者transmitter)和 接收者receiver)的缩写,所以这就是我们将用来绑定这两端变量的名字。这里使用了一个 let 语句和模式来解构了此元组;

现在将发送端移动到一个新建线程中并发送一个字符串,这样新建线程就可以和主线程通讯了,代码如下:

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();});
}

这里再次使用 thread::spawn 来创建一个新线程并使用 movetx 移动到闭包中这样新建线程就拥有 tx 了。新建线程需要拥有信道的发送端以便能向信道发送消息。信道的发送端有一个 send 方法用来获取需要放入信道的值。send 方法返回一个 Result<T, E> 类型,所以如果接收端已经被丢弃了,将没有发送值的目标,所以发送操作会返回错误。在这个例子中,出错的时候调用 unwrap 产生 panic。

我们在主线程中从信道的接收者获取值。这类似于在河的下游捞起橡皮鸭或接收聊天信息,代码如下:

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 received = rx.recv().unwrap();println!("Got: {}", received);
}

信道的接收者有两个有用的方法:recvtry_recv。这里,我们使用了 recv,它是 receive 的缩写。这个方法会阻塞主线程执行直到从信道中接收一个值。一旦发送了一个值,recv 会在一个 Result<T, E> 中返回它。当信道发送端关闭,recv 会返回一个错误表明不会再有新的值到来了。

try_recv 不会阻塞,相反它立刻返回一个 Result<T, E>Ok 值包含可用的信息,而 Err 值代表此时没有任何消息。如果线程在等待消息过程中还有其他工作时使用 try_recv 很有用:可以编写一个循环来频繁调用 try_recv,在有可用消息时进行处理,其余时候则处理一会其他工作直到再次检查。

出于简单的考虑,这个例子使用了 recv;主线程中除了等待消息之外没有任何其他工作,所以阻塞主线程是合适的。

我们将会看到主线程打印出这个值:

2.信道与所有权转移

所有权规则在消息传递中扮演了重要角色,其有助于我们编写安全的并发代码。防止并发编程中的错误是在 Rust 程序中考虑所有权的一大优势。现在让我们做一个试验来看看信道与所有权如何一同协作以避免产生问题:我们将尝试在新建线程中的信道中发送完 val之后 再使用它。

看下面的代码:

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();println!("val is {}", val);});
​let received = rx.recv().unwrap();println!("Got: {}", received);
}

这里尝试在通过 tx.send 发送 val 到信道中之后将其打印出来。但这么做有个隐患:一旦将值发送到另一个线程后,那个线程可能会在我们再次使用它之前就将其修改或者丢弃。其他线程对值可能的修改会由于不一致或不存在的数据而导致错误或意外的结果。

当编译这段代码时,Rust给出一个错误:

上面的隐患会造成一个编译时错误。send 函数获取其参数的所有权并移动这个值归接收者所有。这可以防止在发送后再次意外地使用这个值;所有权系统检查一切是否合乎规则。

3.发送多个值并观察接收者的等待

先看一段代码:

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);}
}

这一次,在新建线程中有一个字符串 vector 希望发送到主线程。我们遍历它们,单独的发送每一个字符串并通过一个 Duration 值调用 thread::sleep 函数来暂停一秒。

在主线程中,不再显式调用 recv 函数:而是将 rx 当作一个迭代器。对于每一个接收到的值,我们将其打印出来。当信道被关闭时,迭代器也将结束。编译这段代码,结果如下:

因为主线程中的 for 循环里并没有任何暂停或等待的代码,所以可以说主线程是在等待从新建线程中接收值。

4.通过克隆发送者创建多个生产者

之前我们提到了mpscmultiple producer, single consumer 的缩写。可以运用 mpsc 来扩展创建向同一接收者发送值的多个线程。这可以通过克隆发送者来做到, 看下面的代码:

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);}

这一次,在创建新线程之前,对发送者调用了 clone 方法。这会给我们一个可以传递给第一个新建线程的发送端句柄。我们会将原始的信道发送端传递给第二个新建线程。这样就会有两个线程,每个线程将向信道的接收端发送不同的消息。编译代码执行结果如下:

不同的系统可能会看到这些值以不同的顺序出现;这也就是并发既有趣又困难的原因。如果通过 thread::sleep 做实验,在不同的线程中提供不同的值,就会发现它们的运行更加不确定,且每次都会产生不同的输出。

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

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

相关文章

VS项目属性变量

VS项目属性变量 $(SolutionDir) 获取解决方案的路径 $(Platform) 平台名字 → x86 / x64 $(ProjectName) 工程名字 $(Configuration) 当前的项目模式 → Debug / Release

No205.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

架构问题自查

一、系统分析 识别和理解业务需求,可从业务全局角度,对需求进行建模设计 可在指导下阅读和理解需求文档、参与需求评审会并可理解需求内容(参与需求分析/评审的相关邮件,会议纪要等) 可阅读和理解需求文档、参与需求评审会并可理解需求内容(参与需求分析/评审的相关邮件,…

主题讲座:全球增材制造现状与未来(暨香港科技大学广州|智能制造学域2024博士学位全额奖学金项目)

时间&#xff1a;2023 年11月16日&#xff08;星期四&#xff09;14:30 地点&#xff1a;合肥工业大学 学术会议中心三楼报告厅 主讲嘉宾&#xff1a;陈模军 助理教授 https://facultyprofiles.hkust-gz.edu.cn/faculty-personal-page/CHEN-Mojun/mjchen 报名表直达&#xff1…

学习samba

文章目录 一、samba介绍二、samba的主要进程三、配置文件四、例子 一、samba介绍 1、SMB&#xff08;Server Message Block&#xff09;协议实现文件共享&#xff0c;也称为CIFS&#xff08;Common Internet File System&#xff09;。 2、是Windows和类Unix系统之间共享文件的…

list复制出新的list后修改元素,也更改了旧的list?

例子 addAll() Testpublic void CopyListTest(){Student student Student.builder().id(1).name("张三").age(23).classId(1).build();Student student2 Student.builder().id(2).name("李四").age(22).classId(1).build();List<Student> student…

Java读取文件内容写入新文件

要实现读写文件这个过程我们需要导入以下的包 import java.io.BufferedReader; import java.io.BufferedWriter;BufferedReader 用于逐行读取源文件的内容&#xff0c;BufferedWriter 用于逐行写入目标文件。 下面以示例了解如何操作&#xff1a; import java.io.BufferedRe…

警方打击了大规模网络钓鱼提供商BulletProftLink

导语 最近&#xff0c;马来西亚皇家警察宣布成功打击了一个名为BulletProftLink的大规模网络钓鱼提供商。这个提供超过300个钓鱼模板的平台被查封&#xff0c;给全球网络安全带来了巨大的利好消息。本文将带您了解这个引人注目的行动背后的故事&#xff0c;并揭示BulletProftLi…

Ubuntu 和 Windows 文件互传

FTP 服务 FTP 采用 Internet 标准文件传输协议 FTP 的用户界面&#xff0c; 向用户提供了一组用来管理计算机之间文件传输的应用程序。在开发的过程中会频繁的在 Windows 和 Ubuntu 下进行文件传输&#xff0c;比如在 Windwos 下进行代码编写&#xff0c;然后将编写好的代码拿到…

Android拖放startDragAndDrop拖拽Glide灵活加载堆叠圆角图,Kotlin(6)

Android拖放startDragAndDrop拖拽Glide灵活加载堆叠圆角图&#xff0c;Kotlin&#xff08;6&#xff09; Android拖放startDragAndDrop拖拽Glide加载堆叠圆角图&#xff0c;Kotlin&#xff08;5&#xff09;-CSDN博客文章浏览阅读1.3k次。&#xfeff;&#xfeff;Android Dyna…

Vue 事件修饰符

Vue 事件修饰符 在 Vue 中&#xff0c;事件修饰符允许我们在处理 DOM 事件时添加一些特殊的修饰符&#xff0c;以便更方便地控制事件的行为。以下是常用的 Vue 事件修饰符&#xff1a; .stop .stop 修饰符用于阻止事件冒泡&#xff0c;即停止事件在父元素之间的传播。 示例…

【C++】:STL——标准模板库介绍 || string类

&#x1f4da;1.什么是STL STL(standard template libaray-标准模板库)&#xff1a;是C标准库的重要组成部分&#xff0c;不仅是一个可复用的组件库&#xff0c;而且 是一个包罗数据结构与算法的软件框架 &#x1f4da;2.STL的版本 原始版本 Alexander Stepanov、Meng Lee 在…

MySQL - 左连接、右连接、内连接、完全外连接、交叉连接 一对多、多对一、多对多 联合连接

前言 做服务端接口开发时&#xff0c;经常对数据库进行查询操作&#xff0c;了解并掌握数据库的左连接、右连接、内连接、完全外连接、交叉连接&#xff0c;一对多、多对一、多对多&#xff0c;联合连接等概念和使用就很重要了。 首先给出两个数据表&#xff0c;分别为学生表…

【深度学习】可交互讲解图神经网络GNN

在正式开始前&#xff0c;先找准图神经网络GNN(Graph Neural Network)的位置。 图神经网络GNN是深度学习的一个分支。 深度学习的四个分支对应了四种常见的数据格式&#xff0c;前馈神经网络FNN处理表格数据&#xff0c;表格数据可以是特征向量&#xff0c;卷积神经网络CNN处理…

Spring,SpringMVC,SpringBoot中注解讲解

文章目录 1 Spring和SpringMVC 注解1.1 SpringMVC 注解1.1.1 RequestMapping1.1.2 RequestBody1.1.3 GetMapping1.1.4 PostMapping1.1.5 PutMapping1.1.6 DeleteMapping1.1.7 PatchMapping1.1.8 ControllerAdvice1.1.9 ResponseBody1.1.10 ExceptionHandler1.1.11 ResponseStat…

嵌入式软件工程师面试题——2025校招社招通用(十四)

说明&#xff1a; 面试题来源于网络书籍&#xff0c;公司题目以及博主原创或修改&#xff08;题目大部分来源于各种公司&#xff09;&#xff1b;文中很多题目&#xff0c;或许大家直接编译器写完&#xff0c;1分钟就出结果了。但在这里博主希望每一个题目&#xff0c;大家都要…

Java 入门基础题

目录 1.输出一个整数的每一位 2.判定素数 3.求最大值方法的重载 4.输出闰年 5.打印 X 图形 6.数字9 出现的次数 7.计算分数的值 8. 模拟登陆 9.使用函数求最大值 10.斐波那契数列 星光不负赶路人&#xff0c;加油铁子们&#xff01;&#xff01;&#xff01; 1…

Elasticsearch 在 Java 开发中的应用

Elasticsearch 在 Java 开发中的应用 Elasticsearch 是一个开源的分布式搜索引擎&#xff0c;广泛应用于全文搜索、日志分析等场景。本文将介绍 Elasticsearch 在 Java 开发中的基本使用和一些实际场景中的应用。 1. 引入 Elasticsearch 依赖 首先&#xff0c;需要在 Maven 或…

RT-DETR算法优化改进:Backbone改进 | VanillaNet一种新视觉Backbone,极简且强大!华为诺亚2023

💡💡💡本文独家改进: VanillaNet助力RT-DETR ,替换backbone,简到极致、浅到极致!深度为6的网络即可取得76.36%@ImageNet的精度,深度为13的VanillaNet甚至取得了83.1%的惊人性能。 推荐指数:五星 RT-DETR魔术师专栏介绍: https://blog.csdn.net/m0_63774211/cat…

11-13 /11-14代理模式 AOP

调用者 代理对象 目标对象 代理对象除了可以完成核心任务&#xff0c;还可以增强其他任务,无感的增强 代理模式目的: 不改变目标对象的目标方法的前提,去增强目标方法 分为:静态代理,动态代理 静态代理 有对象->前提需要有一个类&#xff0c;那么我们可以事先写好一个类&a…