Rust线程中handle的join操作作用及无join时结果异常原因
Rust线程join循环的作用及无join时的输出异常解析
问题描述
以下是Rust官方文档中的并发线程示例代码:
use std::sync::{Arc, Mutex}; use std::thread; fn main() { let counter = Arc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); } // 注释掉下面的join循环后出现异常输出 for handle in handles { handle.join().unwrap(); } println!("Result: {}", *counter.lock().unwrap()); }
注释掉遍历handles调用join的循环后,创建10个线程时输出结果为8,创建1000个线程时输出为999。同时对join循环的逻辑存在疑问,文档说明该操作是确保所有线程执行完毕,想知道这一现象的原因。
补充:相关概念可参考《Rust程序设计语言》第16章第3节“共享状态并发”。
解答
1. join循环的核心作用
每个handle是子线程的句柄,调用handle.join().unwrap()会阻塞当前的主线程,直到对应的子线程完全执行完毕。遍历所有handle调用join,就是强制让主线程等待所有子线程都完成任务后,再继续执行后续的打印逻辑,这是保证所有子线程任务都被执行的可靠方式。
2. 无join时输出异常的本质
如果去掉join循环,主线程不会等待任何子线程,会直接执行到println语句,打印当前的计数器值后,主线程就会结束。而主线程是进程的核心线程,主线程结束意味着整个进程被操作系统终止,所有还没来得及执行完的子线程会被强制杀死——这些被杀死的线程根本没机会执行*num +=1的操作,所以计数器的最终值会小于预期的线程数量。
3. 输出结果不稳定的原因
主线程结束前,有多少子线程能完成任务完全取决于操作系统的线程调度:
- 10个线程的场景中,主线程结束时可能只有8个线程抢到互斥锁并完成了自增,剩下2个线程还没轮到执行就被终止,所以输出8;
- 1000个线程的场景中,绝大多数线程能在主线程结束前完成操作,只有1个线程没赶上调度,所以输出999。
这种结果是随机的,多次运行可能得到不同的数值,比如10线程的场景可能输出7、9甚至偶尔的10(当所有子线程刚好在主线程结束前都执行完)。
4. 为什么不能依赖“运气”得到正确结果
操作系统的线程调度是不可控的,不能假设主线程的打印和退出逻辑会自动等待子线程。只有显式调用join,才能保证所有子线程的任务都被执行完毕,这是编写可靠并发程序的标准做法。
内容的提问来源于stack exchange,提问作者WowBow
相关产品推荐
相关产品推荐

