基本使用
约 957 字大约 3 分钟
2025-09-26
创建新线程
创建一个新的线程需要调用 thread::spawn 函数,并传入一个闭包,闭包中的代码就是期望的在新线程中运行的代码
use std::{thread, 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,它也会在主线程结束时结束
使用 thread::sleep 会强制线程停止执行一小段时间,这会允许其他线程运行。在上述代码中,主线程会首先打印,即便新创建线程的打印语句写在程序的开头。当 Rust 程序的主线程结束时,所有新线程也会随之结束。因此,即使新线程的打印语句预期能执行到 i = 9,它也可能在主线程结束时被提前中止。
等待线程结束
注
上述案例中的代码大部分时候不光会提早结束新建线程(因为无法保证线程运行的顺序),甚至不能实际保证新建线程会被执行。
可以通过将 thread::spawn 的返回值存储在变量中来修复新建线程部分没有执行或者完全没有执行的问题,改函数返回值类型是:JoinHandle<T>,它是一个拥有所有权的值,当对其调用 join 方法时,它会等待其线程结束
use std::{thread, time::Duration};
fn main() {
let handler = 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));
}
handler.join().unwrap();
}通过调用句柄的 join 会阻塞当前线程直到句柄所代表的线程结束。阻塞(Blocking)线程意味着阻止该线程执行工作或退出。因此,即便主线程中的 i = 5 后程序依然会运行直至新建线程执行完毕
思考:如果将 join 方法的调用放在主线程的 for 之前会发生什么
join 会阻塞当前线程(也就是主线程),直到对应的子线程执行完毕并返回,因此,这会改变原本两个线程交错打印的行为,主线程会先等待子线程把 1..10 的打印全部完成,join 返回后主线程才开始执行自己的 for 循环
闭包修饰符 - move
闭包修饰符 move 关键字通常用于传递给 thread::spawn 的闭包,因为闭包会获取从环境中取得的值的所有权,因此会将这些值的所有权从一个线程传送到另一个线程。而 move 关键字会把闭包捕获外部变量的方式,从借用(引用)变为获取所有权
重要
所以将所有权进行转移则是因为如果不加 move,闭包会借用外围变量;但这些变量可能在子线程启动或运行期间已在主线程中 离开作用域被释放,从而产生潜在的悬垂引用。用 move 可将所需数据的所有权转移到子线程,保证数据在子线程的整个生命周期内都有效、可用。若需要在多个线程间共享而非独占,需要结合 Arc(以及 Mutex/RwLock)来共享所有权与可变性