笔记的内容主要参考与《Rust 程序设计语言》,一些也参考了《通过例子学 Rust》和《Rust语言圣经》。
Rust学习笔记分为上中下,其它两个地址在Rust学习笔记(上)和Rust学习笔记(中)。
在大部分现代操作系统中,已执行程序的代码在一个 进程(process)中运行,操作系统则负责管理多个进程。在程序内部,也可以拥有多个同时运行的独立部分。运行这些独立部分的功能被称为 线程(threads)。
将程序中的计算拆分进多个线程可以改善性能,因为程序可以同时进行多个任务,不过这也会增加复杂性。因为线程是同时运行的,所以无法预先保证不同线程中的代码的执行顺序。这会导致诸如此类的问题:
需要调用 thread::spawn
函数并传递一个闭包
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还没执行完,主线程就走到了最后,结束了线程
// thread::spawn的返回值类型是JoinHandle。它是一个拥有所有权的值,当对其调用join方法时,会阻塞当前线程直到 handle 所代表的线程结束
// 如果把这句话放到for i in 1..5这句前面,就会看到并不会交替输出,而是输出完handle里的,才走下面
handle.join().unwrap();
}
其经常与 thread::spawn
一起使用,因为它允许我们在一个线程中使用另一个线程的数据。
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();
}
为什么传值要用move,也就是捕获所有权?
可以避免数据竞争,比如别人也调用了,然后更改了数据。还比如主线程在let handle下面drop了v,结果drop先执行了,那么handle也会发生错误
为什么thread::spawn要用这种闭包的写法?
首先可以利用闭包的一些特性吧,比如move,后面也不用写变量什么的,直接自动全拿,函数的话还得一个一个写
为什么let handle这里直接当成个函数?
因为人家就是这么设计的,这样就能handle.join().unwrap();,而且你也可以单独写个函数包起来
通过发送包含数据的消息来相互沟通,这个思想来源于 Go 编程语言文档中 的口号:“不要通过共享内存来通讯;而是通过通讯来共享内存。” Rust 中一个实现消息传递并发的主要工具是 通道(channel)
编程中的通道有两部分组成,一个发送者(transmitter)和一个接收者(receiver)。当发送者或接收者任一被丢弃时可以认为通道被 关闭(closed)了。
这里使用 mpsc::channel
函数创建一个新的通道;mpsc
是 多个生产者,单个消费者(multiple producer, single consumer)的缩写。简而言之,Rust 标准库实现通道的方式意味着一个通道可以有多个产生值的 发送(sending)端,但只能有一个消费这些值的 接收(receiving)端。
use std::thread;
use std::sync::mpsc;
fn main() {
// 第一个元素是发送端(transmitter),而第二个元素是接收端(receiver)
let (tx, rx) = mpsc::channel();
// 如果想要实现多个发送者,直接clone即可
// let tx1 = tx.clone();
thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap();
});
// 这个方法会阻塞主线程执行直到从通道中接收一个值。一旦发送了一个值,recv会在一个Result中返回它。当通道发送端关闭,recv会返回一个错误表明不会再有新的值到来了。
let received = rx.recv().unwrap();
// try_recv不会阻塞,相反它立刻返回一个Result Ok值包含可用的信息,而Err值代表此时没有任何消息。如果线程在等待消息过程中还有其他工作时使用try_recv很有用:可以编写一个循环来频繁调用try_recv,在有可用消息时进行处理,其余时候则处理一会其他工作直到再次检查。
println!("Got: {}", received);
}
可以发送多个值
use std::thread;
use std::sync::mpsc;
use std::time::Duration