一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第9天,点击查看活动详情。
- 调用
thread::scope()并传入一个闭包。传入的闭包会被直接执行,其中有个参数 s,代表作用域的范围。 - 使用 s 生成新的线程,这些新线程可以借用局部变量,如
numbers。 - 当作用域结束,还没有 join 的线程会被自己 join
这种模式保证了在作用域中生成的线程没有一个能超过作用域。也正因如此,作用域范围的 spawn() 对参数类型没有 ‘static约束,允许我们引用任何东西,只要它的生命周期不小于作用域,比如变量 numbers 。
let mut numbers = vec![1, 2, 3];
thread::scope(|s| {
s.spawn(|| {
numbers.push(1);
});
s.spawn(|| {
numbers.push(2); // Error!
});
});
error[E0499]: cannot borrow `numbers` as mutable more than once at a time
--> example.rs:7:13
|
4 | s.spawn(|| {
| -- first mutable borrow occurs here
5 | numbers.push(1);
| ------- first borrow occurs due to use of `numbers` in closure
|
7 | s.spawn(|| {
| ^^ second mutable borrow occurs here
8 | numbers.push(2);
| ------- second borrow occurs due to use of `numbers` in closure
⚠️ THE LEAKPOCALYPSE
在Rust 1.0之前,标准库中有一个名为 thread::scoped 的函数,可以直接生成一个线程,就像 thread::spawn 一样。它允许非静态捕获,因为它不是一个 JoinHandle,而是返回一个 JoinGuard,当它被Drop时会将执行 join thread。任何借用的数据只需要超过这个 JoinGuard 的生命周期。
这看起来似乎很安全,只要 JoinGuard 在某个时刻Drop即可。
就在Rust 1.0发布之前,人们慢慢发现,不可能保证某样东西会被Drop。这有很多情况,比如引用计数节点的循环,使人们有可能忘记某个东西而不Drop它。
最终,在一些人所说的 *"The Leakpocalypse" *中,thread::scoped 被移除,mem::forget 被变得安全,以强调遗忘(或泄漏)东西总是可能的,不能依赖Drop保证其正确性。
直到很久以后,在Rust 1.??,新的 thread::scoped 被添加进来,而它的设计并不依赖Drop来保证正确性。
Shared Ownership and Reference Counting
目前为止,我们已经研究了如果使用 move 将一个值的所有权转移给线程和从生命周期较长的父线程中借用数据(Scoped Threads)的问题。
而当两个线程之间共享数据时,如果其中一个线程不能保证比另一个线程生命周期长,那么它们中的任何一个都不能成为该数据的所有者。最佳选择:他们之间共享的任何数据的lifetime都需要和生命周期较长的线程一样长。
Statics
有几种方法可以创建不被单个线程所拥有的变量。最简单的是一个 static value,它被整个程序所拥有,而不是一个单独的线程。在下面的例子中,两个线程都可以访问X,但它们都不拥有它:
static X: [i32; 3] = [1, 2, 3];
thread::spawn(|| dbg!(&X));
thread::spawn(|| dbg!(&X));
静态项有一个常量初始化器,并且永远不会被 drop,并且在程序的main函数开始执行之前就已经存在。而且每个线程都可以借用它,因为它被保证永远存在。
TODO!!!