bigywater.tech
Rust · 87

线程创建与 Join

thread::spawn 看起来简单,实际上有 move、join、栈大小、不确定顺序等一连串规则。这篇每一条都用真实运行验证:什么时候要 move、不 join 会怎样、顺序真的不确定吗、scope 又好在哪、栈溢出会怎样。

3 / 8
同一程序 8 次运行得到的不同执行顺序数
约 25 µs
spawn + join 一个空线程的平均耗时(release)
abort
线程栈溢出的结局,不是可捕获的 panic

JoinHandle 与线程名

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    主线程名 Some("main")  子线程名 None  两个线程 id 不同: true  子线程算出 55
    Builder 命名的线程: worker-7

move 与 spawn

实测 · 本机执行输出
  源码:
      1 | use std::thread;
      2 | fn main() { let v = vec![1, 2, 3]; let h = thread::spawn(|| { println!("{:?}", v); }); h.join().unwrap(); }
  编译:失败
    error[E0373]: closure may outlive the current function, but it borrows `v`, which is owned by the current function
     --> <实验目录>/src/u87b.rs:2:58
      |
    2 | fn main() { let v = vec![1, 2, 3]; let h = thread::spawn(|| { println!("{:?}", v); }); h.join().unwrap(); }
      |                                                          ^^                    - `v` is borrowed here
      |                                                          |
      |                                                          may outlive borrowed value `v`
      |
    note: function requires argument type to outlive `'static`
     --> <实验目录>/src/u87b.rs:2:44
      |
    2 | fn main() { let v = vec![1, 2, 3]; let h = thread::spawn(|| { println!("{:?}", v); }); h.join().unwrap(); }
      |                                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword
      |
    2 | fn main() { let v = vec![1, 2, 3]; let h = thread::spawn(move || { println!("{:?}", v); }); h.join().unwrap(); }
      |                                                          ++++
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0373`.
  源码:
      1 | use std::thread;
      2 | fn main() { let v = vec![1, 2, 3]; let h = thread::spawn(move || { println!("{:?}", v); }); println!("{:?}", v); h.join().unwrap(); }
  编译:失败
    error[E0382]: borrow of moved value: `v`
     --> <实验目录>/src/u87c.rs:2:110
      |
    2 | fn main() { let v = vec![1, 2, 3]; let h = thread::spawn(move || { println!("{:?}", v); }); println!("{:?}", v); h.join().unwrap(); }
      |                 -                                        -------                    -                        ^ value borrowed here after move
      |                 |                                        |                          |
      |                 |                                        |                          variable moved due to use in closure
      |                 |                                        value moved into closure here
      |                 move occurs because `v` has type `Vec<i32>`, which does not implement the `Copy` trait
      |
    help: consider cloning the value before moving it into the closure
      |
    2 ~ fn main() { let v = vec![1, 2, 3]; let value = v.clone();
    3 ~ let h = thread::spawn(move || { println!("{:?}", value); }); println!("{:?}", v); h.join().unwrap(); }
      |
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0382`.

Send 的要求见Send 与 Sync。

不 join 会怎样

实测 · 本机执行输出
  编译:成功
  参数 (): stdout = ['主线程到达末尾(join = false)']
  参数 ('join',): stdout = ['主线程到达末尾(join = true)', '子线程跑完了']

执行顺序

实测 · 本机执行输出
  编译:成功
  8 次运行的顺序: ['04123765', '01234567', '01234567', '01234567', '01234567', '01234567', '01324567', '01234567'] (共 3 种不同顺序)

thread::scope

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    scope 里两个线程分别改 data 的左右两半:data = [10, 20, 30, 5, 6, 7] 合计 78
    spawn + join 一个空线程平均 25 µs(release,2000 次)

栈大小

实测 · 本机执行输出
  编译:成功
  栈 2048 KiB:退出码 0;stdout='栈 2048 KiB:递归 2000 层成功 → 2000';stderr 末行=[]
  栈 512 KiB:被信号 6 终止;stdout='';stderr 末行=['fatal runtime error: stack overflow, aborting']
  栈 256 KiB:被信号 6 终止;stdout='';stderr 末行=['fatal runtime error: stack overflow, aborting']
  栈 64 KiB:被信号 6 终止;stdout='';stderr 末行=['fatal runtime error: stack overflow, aborting']

分步演示

线程
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。耗时 release 构建
    • 没有验证:默认栈大小的具体数值(只验证了 2048 KiB 够用、512 KiB 不够这个例子)、线程数上限、线程局部存储、park/unpark、线程优先级与亲和性、join 在子线程 panic 时的返回(见 panic 一篇)
    京ICP备2026067502号-1京公网安备11010502063487号