并发基础
线程
与 C++ 的 std::thread::thread
类似, Rust 也可以通过函数 [std::thread::spawn
] 来创建线程.
返回的 JoinHandle 在被释放后会分离, 如果此时主线程结束, 也将导致线程被强制结束. 可以通过调用其 join
函数来堵塞调用者线程, 并等待该线程结束.
use std::thread;
fn main() {
let t1 = thread::spawn(f);
let t2 = thread::spawn(f);
println!("Main thread");
t1.join().unwrap();
t2.join().unwrap();
}
fn f() {
let id = thread::current().id();
println!("Thread id: {id:?}");
}
Rust 的 println
宏与 C++ 的 cout
一样的同步的, 所以即使在并发执行, 也能保证内容完整 (不被打断) 的输出.
一种常见的做法是向传递一个闭包:
let numbers = vec![1, 2, 3];
thread::spawn(move || {
for n in &numbers {
println!("{n}");
}
}).join().unwrap();
如果闭包需要访问外部变量则必须使用 move
关键字将变量的所有权移交给闭包.
如果缺省 move
, 闭包将默认通过引用访问外部变量, 但闭包的作用域和外部变量不一致.
上面代码创建的线程立即调用了 join
函数, 所以不存在外部变量被提前释放的情况. 但 Rust 编译器无法识别这一点, 后续会介绍作用域线程, 可以解决这一问题.
与 std::fs::OpenOptions
类似, 线程也可以通过 [std::thread::Builder
] 创建. 该方法可以在线程生成前进行一些设置, 比如设置线程的名字, 后续可以在线程内通过 std::thread::current().name()
获取.
[std::thread::spawn
] 函数的实现便是通过调用 [std::thread::Builder
], 并使用默认的设置来生成线程.
作用域线程 (Scoped threads)
let numbers = vec![1, 2, 3];
thread::scope(|s| {
s.spawn(|| {
println!("length: {}", numbers.len());
});
s.spawn(|| {
for n in &numbers {
println!("{n}");
}
});
});
使用作用域线程则无需转移外部变量的所有权, 因为所创建的线程都将自动 join
.