Rust从入门到实战系列一百八十七:线程与 move 闭包

move 关键字经常用于传递给 thread::spawn 的闭包,因为闭包会获取从环境中取得的值的所有权,因
此会将这些值的所有权从一个线程传送到另一个线程。在第十三章 ” 闭包会捕获其环境” 部分讨论了闭包
上下文中的 move。现在我们会更专注于 move 和 thread::spawn 之间的交互。
在第十三章中,我们讲到可以在参数列表前使用 move 关键字强制闭包获取其使用的环境值的所有权。
这个技巧在创建新线程将值的所有权从一个线程移动到另一个线程时最为实用。
注意示例 16-1 中传递给 thread::spawn 的闭包并没有任何参数:并没有在新建线程代码中使用任何主线
程的数据。为了在新建线程中使用来自于主线程的数据,需要新建线程的闭包获取它需要的值。示例 16-3
展示了一个尝试在主线程中创建一个 vector 并用于新建线程的例子,不过这么写还不能工作,如下所示:
文件名: src∕main.rs
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(|| {
println!(“Here’s a vector: {:?}”, v);
});
handle.join().unwrap();
}
示例 16-3: 尝试在另一个线程使用主线程创建的 vector
闭包使用了 v,所以闭包会捕获 v 并使其成为闭包环境的一部分。因为 thread::spawn 在一个新线程中
运行这个闭包,所以可以在新线程中访问 v。然而当编译这个例子时,会得到如下错误:
$ cargo run
Compiling threads v0.1.0 (file:///projects/threads)
error[E0373]: closure may outlive the current function, but it borrows v, which is owned by the current function
–> src/main.rs:6:32
|
6 | let handle = thread::spawn(|| {
| ^^ may outlive borrowed value v
7 | println!(“Here’s a vector: {:?}”, v);
| - v is borrowed here
|
note: function requires argument type to outlive 'static
–> src/main.rs:6:18
|
6 | let handle = thread::spawn(|| {
| ____________^
7 | | println!(“Here’s a vector: {:?}”, v);
8 | | });
| |
^
help: to force the closure to take ownership of v (and any other referenced variables), use the move keyword
|
6 | let handle = thread::spawn(move || {
| ++++
For more information about this error, try rustc --explain E0373.
error: could not compile threads due to previous error
Rust 会 推断如何捕获 v,因为 println! 只需要 v 的引用,闭包尝试借用 v。然而这有一个问题:Rust 不
知道这个新建线程会执行多久,所以无法知晓 v 的引用是否一直有效。
示例 16-4 展示了一个 v 的引用很有可能不再有效的场景:
文件名: src∕main.rs
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(|| {
println!(“Here’s a vector: {:?}”, v);
});
drop(v); // oh no!
handle.join().unwrap();
}
示例 16-4: 一个具有闭包的线程,尝试使用一个在主线程中被回收的引用 v
假如这段代码能正常运行的话,则新建线程则可能会立刻被转移到后台并完全没有机会运行。新建线程
内部有一个 v 的引用,不过主线程立刻就使用第十五章讨论的 drop 丢弃了 v。接着当新建线程开始执
行,v 已不再有效,所以其引用也是无效的。噢,这太糟了!
为了修复示例 16-3 的编译错误,我们可以听取错误信息的建议:
help: to force the closure to take ownership of v (and any other referenced variables), use the move keyword
|
6 | let handle = thread::spawn(move || {
| ++++
通过在闭包之前增加 move 关键字,我们强制闭包获取其使用的值的所有权,而不是任由 Rust 推断它
应该借用值。示例 16-5 中展示的对示例 16-3 代码的修改,可以按照我们的预期编译并运行:
文件名: src∕main.rs
use std::thread;
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!(“Here’s a vector: {:?}”, v);
});
handle.join().unwrap();
}
示例 16-5: 使用 move 关键字强制获取它使用的值的所有权
那么如果使用了 move 闭包,示例 16-4 中主线程调用了 drop 的代码会发生什么呢?加了 move 就搞定
了吗?不幸的是,我们会得到一个不同的错误,因为示例 16-4 所尝试的操作由于一个不同的原因而不
被允许。如果为闭包增加 move,将会把 v 移动进闭包的环境中,如此将不能在主线程中对其调用 drop
了。我们会得到如下不同的编译错误:
$ cargo run
Compiling threads v0.1.0 (file:///projects/threads)
error[E0382]: use of moved value: v
–> src/main.rs:10:10
|
4 | let v = vec![1, 2, 3];
| - move occurs because v has type Vec<i32>, which does not implement the Copy trait
5 |
6 | let handle = thread::spawn(move || {
| ------- value moved into closure here
7 | println!(“Here’s a vector: {:?}”, v);
| - variable moved due to use in closure

10 | drop(v); // oh no!
| ^ value used here after move
For more information about this error, try rustc --explain E0382.
error: could not compile threads due to previous error
Rust 的所有权规则又一次帮助了我们!示例 16-3 中的错误是因为 Rust 是保守的并只会为线程借用 v,
这意味着主线程理论上可能使新建线程的引用无效。通过告诉 Rust 将 v 的所有权移动到新建线程,我
们向 Rust 保证主线程不会再使用 v。如果对示例 16-4 也做出如此修改,那么当在主线程中使用 v 时就
会违反所有权规则。move 关键字覆盖了 Rust 默认保守的借用,但它不允许我们违反所有权规则。
现在我们对线程和线程 API 有了基本的了解,让我们讨论一下使用线程实际可以 做什么吧。

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值