多线程
前端同学对于 WebWorker 肯定比较熟悉,对于计算量大的业务,我们可以将计算逻辑分配到多个线程去处理,减少主线程的压力,提高处理速度,在 rust 中启用多线程很方便,如果用 JS 的话来说,启动一个线程就像传递一个回调函数一样简单
使用 spawn 创建新线程
使用标准库中的 thread 模块创建一个 spawn 子线程:
use std::thread; // 引入threaduse 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); // 销毁nhandle.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),也是不允许的。
评论