【Rust】——使用消息在线程之间传递数据

Y小夜 2024-07-20 08:05:01 阅读 53

💻博主现有专栏:

                C51单片机(STC89C516),c语言,c++,离散数学,算法设计与分析,数据结构,Python,Java基础,MySQL,linux,基于HTML5的网页设计及应用,Rust(官方文档重点总结),jQuery,前端vue.js,Javaweb开发,Python机器学习等

🥏主页链接:

                Y小夜-CSDN博客

目录

🎯信道与所有权转移

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

🎯通过克隆发送者来创建多个生产者


学习推荐:

        在当今这个飞速发展的信息时代,人工智能(AI)已经成为了一个不可或缺的技术力量,它正在逐步改变着我们的生活、工作乃至整个社会的运作方式。从智能语音助手到自动驾驶汽车,从精准医疗到智慧城市,人工智能的应用已经渗透到了我们生活的方方面面。因此,学习和掌握人工智能相关的知识和技能,对于任何希望在这个时代保持竞争力的个人来说,都已经变得至关重要。

        然而,人工智能是一个涉及数学、计算机科学、数据科学、机器学习、神经网络等多个领域的交叉学科,其学习曲线相对陡峭,对初学者来说可能会有一定的挑战性。幸运的是,随着互联网教育资源的丰富,现在有大量优秀的在线平台和网站提供了丰富的人工智能学习材料,包括视频教程、互动课程、实战项目等,这些资源无疑为学习者打开了一扇通往人工智能世界的大门。

        前些天发现了一个巨牛的人工智能学习网站:前言 – 人工智能教程通俗易懂,风趣幽默,忍不住分享一下给大家。


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

        你可以将编程中的信道想象为一个水流的渠道,比如河流或小溪。如果你将诸如橡皮鸭或小船之类的东西放入其中,它们会顺流而下到达下游。

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

        这里,我们将开发一个程序,它会在一个线程生成值向信道发送,而在另一个线程会接收值并打印出来。这里会通过信道在线程间发送简单值来演示这个功能。一旦你熟悉了这项技术,你就可以将信道用于任何相互通信的任何线程,例如一个聊天系统,或利用很多线程进行分布式计算并将部分计算结果发送给一个线程进行聚合。

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

<code>use std::sync::mpsc;

fn main() {

let (tx, rx) = mpsc::channel();

}

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

🎯信道与所有权转移

        所有权规则在消息传递中扮演了重要角色,其有助于我们编写安全的并发代码。防止并发编程中的错误是在 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 会给出一个错误:

$ cargo run

Compiling message-passing v0.1.0 (file:///projects/message-passing)

error[E0382]: borrow of moved value: `val`

--> src/main.rs:10:31

|

8 | let val = String::from("hi");

| --- move occurs because `val` has type `String`, which does not implement the `Copy` trait

9 | tx.send(val).unwrap();

| --- value moved here

10 | println!("val is {}", val);

| ^^^ value borrowed here after move

|

= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0382`.

error: could not compile `message-passing` due to previous error

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

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

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 当作一个迭代器。对于每一个接收到的值,我们将其打印出来。当信道被关闭时,迭代器也将结束。

Got: hi

Got: from

Got: the

Got: thread

🎯通过克隆发送者来创建多个生产者

之前我们提到了mpsc是 multiple producer, single consumer 的缩写。

// --snip--

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

}

// --snip--

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

        虽然你可能会看到这些值以不同的顺序出现;这依赖于你的系统。这也就是并发既有趣又困难的原因。如果通过 

thread::sleep 做实验,在不同的线程中提供不同的值,就会发现它们的运行更加不确定,且每次都会产生不同的输出。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。