Rust编程中的线程间通信

news2025/1/11 15:56:31

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.coloradmin.cn/o/1203647.html

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

相关文章

【C++ 学习 ㊱】- 智能指针详解

目录 一、为什么需要智能指针&#xff1f; 二、智能指针的原理及使用 三、auto_ptr 3.1 - 基本使用 3.2 - 模拟实现 四、unique_ptr 4.1 - 基本使用 4.2 - 模拟实现 五、shared_ptr 5.1 - 基本使用 5.2 - 模拟实现 六、weak_ptr 6.1 - shared_ptr 的循环引用问题 …

【Python小程序】求解2 * 2矩阵的逆矩阵

一、内容简介 使用Python求解2 * 2矩阵的逆矩阵。 二、求解方法 我们使用邻接矩阵法来求解2 * 2矩阵的逆矩阵。 det(A): 矩阵A的行列式 adj(A): 矩阵A的邻接矩阵 对于2*2矩阵A 我们有 三、Python代码 基于上述求解方法&#xff0c;我们可以写出Python代码如下&#xff…

行情分析——加密货币市场大盘走势(11.13)

大饼上涨太快&#xff0c;又开始震荡&#xff0c;但上不去&#xff0c;所以目前来看差不多要做回踩动作&#xff0c;入场空单性价比较高。而且从MACD日线来看&#xff0c;也是进入空头趋势&#xff0c;RSI&#xff08;14&#xff09;也是进入了超买区间&#xff0c;值得入手空单…

乡镇村污水处理智慧水务智能监管平台,助力污水监管智慧化、高效化

一、背景与需求 随着城市化进程的加速&#xff0c;排放的污水量也日益增加&#xff0c;导致水污染严重。深入打好污染防治攻坚战的重要抓手&#xff0c;对于改善城镇人居环境&#xff0c;推进城市治理体系和治理能力现代化&#xff0c;加快生态文明建设&#xff0c;推动高质量…

拼多多商品详情API接口接入流程如下:

拼多多商品详情API接口可以用于获取拼多多商品的具体信息&#xff0c;包括商品ID、商品名称、价格、销量、评价等。以下是使用拼多多商品详情API接口的步骤&#xff1a; 进入拼多多开放平台&#xff0c;注册并登录账号。在开放平台页面中&#xff0c;找到“商品详情”或“商品…

基于SSM+Vue的电子商城的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

基因检测技术的发展与创新:安全文件数据传输的重要作用

基因是生命的密码&#xff0c;它决定了我们的身体特征、健康状况、疾病风险等。随着基因检测技术的高速发展&#xff0c;我们可以通过对基因进行测序、分析和解读&#xff0c;更深入地认识自己&#xff0c;预防和治疗各种遗传性疾病&#xff0c;甚至实现个性化医疗和精准健康管…

动态调整学习率Lr

动态调整学习率Lr 0 引入1 代码例程1.1 工作方式解释 2 动态调整学习率的几种方法2.1 lr_scheduler.LambdaLR2.2 lr_scheduler.StepLR2.3 lr_scheduler.MultiStepLR2.4 lr_scheduler.ExponentialLR2.2.5 lr_scheduler.CosineAnnealingLR2.6 lr_scheduler.ReduceLROnPlateau2.7 …

ASK、PSK、FSK的调制与解调

ASK、PSK、FSK的调制与解调 本文主要涉及数字信号的调制与解调&#xff0c;内容包括&#xff1a;2ASK、2PSK、2FSK的调制与解调以及频谱分析 关于通信原理还有其他文章可参考&#xff1a; 1、信息量、码元、比特、码元速率、信息速率详细解析——实例分析 2、模拟系统的AM信号的…

2023最新版JavaSE教程——第6天:面向对象编程(基础)

目录 一、面向对象编程概述(了解)1.1 程序设计的思路1.2 由实际问题考虑如何设计程序1.3 如何掌握这种思想&#xff1f; 二、Java语言的基本元素&#xff1a;类和对象2.1 引入2.2 类和对象概述2.3 类的成员概述2.4 面向对象完成功能的三步骤(重要)2.4.1 步骤1&#xff1a;类的定…

数据分类分级方法及典型应用场景

1 2021-09-29 来源&#xff1a;数据学堂 [打印本稿][字号 大 中小] 《数据安全法》的第二十一条明确规定了由国家建立数据分类分级保护制度&#xff0c;根据数据在经济社会发展中的重要程度&#xff0c;以及一旦遭到篡改、破坏、泄露或者非法获取、非法利用&#xff0c;对国…

Flutter实践二:repository模式

1.repository 几乎所有的APP&#xff0c;从简单的到最复杂的&#xff0c;在它们的架构里几乎都包括状态管理和数据源这两部分。状态管理常见的有Bloc、Cubit、Provider、ViewModel等&#xff0c;数据源则是一些直接和数据库或者网络客户端进行交互&#xff0c;取得相应的数据&…

【开源】基于Vue和SpringBoot的智能停车场管理系统

项目编号&#xff1a; S 005 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S005&#xff0c;文末获取源码。} 项目编号&#xff1a;S005&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容A. 车主端功能B. 停车工作人员功能C. 系…

智能井盖传感器具有什么效果?

智能井盖传感器与智慧城市之间有着密切的关联&#xff0c;两者之间属于相辅相成的状态&#xff0c;对于城市的现代化和城市生命线建设有助力作用。智能井盖传感器是其中一个重要的组成环节&#xff0c;它们帮助城市改变原有的生活和生态环境&#xff0c;为政府部门完善城市基础…

Linux(命令)——结合实际场景的命令 查找Java安装位置命令

前言 在内卷的时代&#xff0c;作为开发的程序员也需要懂一些Linux相关命令。 本篇博客结合实际应用常见&#xff0c;记录Linux命令相关的使用&#xff0c;持续更新&#xff0c;希望对你有帮助。 目录 前言引出一、查找Java安装位置命令1、使用which命令2、使用find命令3、查…

从0到0.01入门React | 006.精选 React 面试题

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

ESP32 Arduino引脚分配参考:您应该使用哪些 GPIO 引脚?

ESP32 芯片有 48 个引脚&#xff0c;具有多种功能。并非所有 ESP32 开发板中的所有引脚都暴露出来&#xff0c;有些引脚无法使用。 关于如何使用 ESP32 GPIO 有很多问题。您应该使用什么引脚&#xff1f;您应该避免在项目中使用哪些引脚&#xff1f;这篇文章旨在成为 ESP32 GP…

【机器学习】 朴素贝叶斯算法:原理、实例应用(文档分类预测)

1. 算法原理 1.1 朴素贝叶斯方法 朴素贝叶斯方法涉及一些概率论知识&#xff0c;我们先来复习一下。 联合概率&#xff1a;包含多个条件&#xff0c;并且所有的条件同时成立的概率&#xff0c;公式为&#xff1a; 条件概率&#xff1a;事件A在另一个事件B已经发生的前提下发…

零代码搭建:无需编程基础,轻松搭建数据自己的能源监测管理平台

零代码搭建能源管理平台&#xff0c;其核心是通过使用图形用户界面和可视化建模工具&#xff0c;来减少编写代码的工作量以及技能要求。平台拥有丰富的预定义组件&#xff0c;可以帮助管理人员快速构建应用程序。并可自定义区域框架&#xff0c;在搭建自己区域时&#xff0c;能…

视频监控系统EasyCVR平台播放告警录像时,播放器显示不全是什么原因?

防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安防…