多线程
前端同学对于 WebWorker 肯定比较熟悉,对于计算量大的业务,我们可以将计算逻辑分配到多个线程去处理,减少主线程的压力,提高处理速度,在 rust 中启用多线程很方便,如果用 JS 的话来说,启动一个线程就像传递一个回调函数一样简单
使用 spawn 创建新线程
使用标准库中的 thread 模块创建一个 spawn 子线程:
use std::thread; // 引入thread
use std::time::Duration; // 引入time::Duration,用来创建时间类型数据
thread::spawn(|| {
for i in 1..10 {
println!("spawn: {}", i);
// Duration::from_secs方法创建单位为秒的时间类型数据
// 使用sleep方法让spawn线程停止一秒
thread::sleep(Duration::from_secs(1));
}
});
for i in 1..5 {
println!("main: {}", i);
// 让主线程线程停止一秒
thread::sleep(Duration::from_secs(1));
}
// main: 1
// spawn: 1
// main: 2
// spawn: 2
// main: 3
// spawn: 3
// main: 4
// spawn: 4
// spawn: 5
复制代码
当主线程结束时,spawn 线程也会结束,而不管其是否执行完毕,上面 spawn 线程并没有执行完成。
使用 join 等待所有线程结束
我们可以使用 thread::spawn 返回值的 join 方法控制让 main 线程去等待 spawn 线程的执行完成:
// 获取spawn线程管理工具
let handle = thread::spawn(|| {
for i in 1..10 {
println!("spawn: {}", i);
thread::sleep(Duration::from_secs(1));
}
});
for i in 1..5 {
println!("main: {}", i);
thread::sleep(Duration::from_secs(1));
}
// 阻塞主线程,等待spawn线程执行
handle.join().unwrap();
// main: 1
// spawn: 1
// main: 2
// spawn: 2
// main: 3
// spawn: 3
// spawn: 4
// main: 4
// spawn: 5
// spawn: 6
// spawn: 7
// spawn: 8
// spawn: 9
复制代码
join 会阻塞当前线程直到 handle 线程结束,阻塞(Blocking)线程意味着阻止该线程执行工作或退出。
如果将 handle.join()移动到 for 循环之前呢?可以猜一下是怎么输出的:
// 略...
handle.join().unwrap();
for i in 1..5 {
println!("main: {}", i);
thread::sleep(Duration::from_secs(1));
}
复制代码
输出结果:
// spawn: 1
// spawn: 2
// spawn: 3
// spawn: 4
// spawn: 5
// spawn: 6
// spawn: 7
// spawn: 8
// spawn: 9
// main: 1
// main: 2
// main: 3
// main: 4
复制代码
可以看到 spawn 线程的循环会先执行完成,再执行主线程的循环。
线程与 move 闭包
上面的 spawn 线程中的代码对 main 线程中的数据没有引用,当 spawn 线程使用 main 线程中的数据时:
let n = vec![1,2,3];
let handle = thread::spawn(|| { // 报错,闭包可能比当前函数存活的时间长,但闭包它借用了'n',而'n'是当前函数拥有的
println!("来自main线程的数据: {:?}", n);
});
handle.join().unwrap();
复制代码
上边闭包尝试借用 v。然而这有一个问题:rust 不知道这个新建线程会执行多久,所以无法知晓 v 的引用是否一直有效,例如:
let n = vec![1,2,3];
let handle = thread::spawn(|| {
println!("来自main线程的数据: {:?}", n);
});
drop(n); // 销毁n
handle.join().unwrap();
复制代码
因为当线程中对 n 有借用,在线程还没执行的时候,后边的 drop 已经将 n 丢弃了。
我们可以通过在闭包之前增加 move 关键字,强制闭包获取其使用的 n 的所有权:
let n = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("来自main线程的数据: {:?}", n); // 来自main线程的数据: [1, 2, 3]
});
handle.join().unwrap();
复制代码
上面代码中,将 n 的所有权移动到了 spawn 闭包中,所以能够正常执行。
如果仍然使用 drop 的话:
let n = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("来自main线程的数据: {:?}", n);
});
drop(n); // 报错,n已经被移动到上边的闭包中,不能再次在这里使用
handle.join().unwrap();
复制代码
因为 n 已经被移动到了 spawn 闭包中,所以不能在后面以任何方式继续使用,即使 println!("{:?}",n),也是不允许的。
评论