Rust 学习笔记:Thread
Rust 学习笔记:Thread
在大多数当前的操作系统中,已执行的程序代码在一个进程中运行,操作系统将同时管理多个进程。在程序中,也可以有同时运行的独立部分。运行这些独立部件的功能称为线程。
将程序中的计算拆分为多个线程以同时运行多个任务可以提高性能,但也增加了复杂性。因为线程可以同时运行,所以不能保证不同线程上代码部分的运行顺序。这可能会导致一些问题,比如:
- 竞争条件,线程以不一致的顺序访问数据或资源
- 死锁,两个线程相互等待
- 只在某些情况下发生的 bug,难以可靠地重现和修复
Rust 标准库使用 1:1 的线程实现模型,即程序中的一个语言线程使用一个操作系统线程。
用 spawn 创建一个新线程
要创建一个新线程,我们调用 thread::spawn 函数并传递给它一个闭包,其中包含我们想要在新线程中运行的代码。
use std::thread;
use std::time::Duration;
fn main() {
thread::spawn(|| {
for i in 1..10 {
println!("hi number {i} from the spawned thread!");
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("hi number {i} from the main thread!");
thread::sleep(Duration::from_millis(1));
}
}
程序输出:

对 thread::sleep 的调用强制线程在短时间内停止执行,允许另一个线程运行。
请注意,当 Rust 程序的主线程完成时,所有派生线程都被关闭,无论它们是否已经完成运行。尽管我们告诉派生线程打印到 i = 9,但在主线程关闭之前,它只打印到 5。
使用 join 句柄等待所有线程完成
通过将 thread::spawn 的返回值保存在一个变量中,我们可以解决派生线程不运行或过早结束的问题。
thread::spawn 的返回类型是 JoinHandle<T>,是一个句柄。在句柄上调用 join 会阻塞当前运行的线程,直到该句柄所表示的线程终止为止。
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!("hi number {i} from the spawned thread!");
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("hi number {i} from the main thread!");
thread::sleep(Duration::from_millis(1));
}
handle.join().unwrap();
}
程序输出:

主线程和 handle 线程交替进行,但是主线程因为调用 handle.join() 而等待,直到派生线程完成才结束。
让我们看看当我们把 handle.join() 移到 main 中的 for 循环之前会发生什么,就像这样:
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!("hi number {i} from the spawned thread!");
thread::sleep(Duration::from_millis(1));
}
});
handle.join().unwrap();
for i in 1..5 {
println!("hi number {i} from the main thread!");
thread::sleep(Duration::from_millis(1));
}
}
程序输出:

主线程将等待派生线程完成,然后运行它的 for 循环,这样输出就不会再交错了。
通过线程使用 move 闭包
我们经常将 move 关键字与传递给 thread::spawn 的闭包一起使用,因为闭包将从环境中获取它使用的值的所有权,从而将这些值的所有权从一个线程转移到另一个线程。
在派生线程中使用来自主线程的数据,派生线程的闭包必须捕获它所需的值。下列代码展示了在主线程中创建一个 vector 并在派生线程中使用它的尝试。
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(|| {
println!("Here's a vector: {v:?}");
});
handle.join().unwrap();
}
闭包使用 v,因此它将捕获 v 并使其成为闭包环境的一部分。因为 thread::spawn 在一个新线程中运行这个闭包,我们应该能够在这个新线程中访问 v。
然而,运行报错了:

Rust 无法判断派生线程将运行多长时间,因此它不知道对 v 的引用是否总是有效的。例如,我们可能在 join() 前就销毁了 v。
通过在闭包之前添加 move 关键字,我们强制闭包获得它所使用的值的所有权,而不是让 Rust 推断它应该借用这些值。
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 关键字后,程序可以成功运行了。
通过告诉 Rust 将 v 的所有权转移到派生线程,我们向 Rust 保证主线程不再使用 v。如果我们还试图在主线程中使用 v,就违反了所有权规则,得到编译错误:use of moved value: `v`。
291

被折叠的 条评论
为什么被折叠?



