提问人:Fajela Tajkiya 提问时间:9/14/2023 最后编辑:Aleksander KrauzeFajela Tajkiya 更新时间:9/14/2023 访问量:49
如果当主函数结束时整个过程结束时,闭包如何比主函数寿命长?
How can a closure outlive the main function if the entire process ends when main function ends?
问:
我有以下代码:
use std::thread;
fn main() {
let x: &'static mut [i32; 3] = Box::leak(Box::new([1, 2, 3]));
thread::spawn(|| dbg!(&x));
}
当我编译它时,我得到以下错误:
error[E0373]: closure may outlive the current function, but it borrows `x`, which is owned by the current function
--> src\main.rs:10:19
|
10 | thread::spawn(|| dbg!(&x));
| ^^ - `x` is borrowed here
| |
| may outlive borrowed value `x`
|
note: function requires argument type to outlive `'static`
--> src\main.rs:10:5
|
10 | thread::spawn(|| dbg!(&x));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
help: to force the closure to take ownership of `x` (and any other referenced variables), use the `move` keyword
|
10 | thread::spawn(move || dbg!(&x));
| ++++
在这里,它告诉我“闭包可能比当前功能更长寿”。我想“当前函数”是主要函数。但是,闭合是否有可能超过主要功能?当 main 函数结束时,整个过程也结束。这意味着从此进程生成的所有线程也会终止,无论它们是否已完成执行。那么,闭合如何才能比主要功能更持久呢?
答:
线程在 main 函数结束后被终止。
这听起来像是吹毛求疵,但这种区别很重要,因为 main 函数的结尾包括局部变量的破坏。这意味着首先变量被销毁,包括调用它们的实现,然后线程被杀死。drop()
这证明了这一事实:
use std::{
thread::{self, sleep},
time::Duration,
};
struct MyStruct {}
impl Drop for MyStruct {
fn drop(&mut self) {
println!("Drop");
sleep(Duration::from_millis(100));
}
}
fn main() {
let _x = MyStruct {};
thread::spawn(|| loop {
sleep(Duration::from_millis(45));
println!("Thread");
});
sleep(Duration::from_millis(100));
}
Thread
Thread
Drop
Thread
Thread
请注意,如果线程在函数内部有一个(非)对 的引用,则 a 和 a to 将同时存在。此外,变量包含的任何内容肯定会在函数中被销毁,因此对它的外部引用绝对是未定义的行为。&
mut
_x
drop()
&
&mut
_x
drop()
出于这样的原因,该函数被简单地视为正常函数,并且所有正常的借用规则都适用。main()
这些是关于为什么被视为普通函数的一些通用词,但现在让我们谈谈您的代码。main()
看来您试图通过 ing 变量来克服这个问题。这绝对是一种将变量的生存期延长到 结束之后的可能方法,但你犯了一个错误。数组本身是 ,但变量不是。当你在线程中这样做时,你引用的不是数组,而是变量 ,并且位于 中。Box::leak
main()
&'static
x
&x
x
x
main()
您需要将引用本身移动到线程中。因为这与将自身移动到线程中有什么不同并不明显,所以我将创建第二个线程进行演示:Box
use std::{
thread::{self, sleep},
time::Duration,
};
fn main() {
let x: &'static [i32; 3] = Box::leak(Box::new([1, 2, 3]));
let x2 = x;
thread::spawn(move || dbg!(x));
thread::spawn(move || dbg!(x2));
sleep(Duration::from_millis(10));
}
[src\main.rs:10] x = [
1,
2,
3,
]
[src\main.rs:11] x2 = [
1,
2,
3,
]
我还引用了 ,而不是 ,供两个线程使用。
另请注意,引用是 ,使成为可能。这不会克隆原始数组,它们都指向完全相同的数据。&
&mut
&
Copy
let x2 = x;
当与 配对时,这变得很明显:Mutex
use std::{
sync::Mutex,
thread::{self, sleep},
time::Duration,
};
fn main() {
let x: &'static Mutex<[i32; 3]> = Box::leak(Box::new(Mutex::new([1, 2, 3])));
let x2 = x;
thread::spawn(move || {
sleep(Duration::from_millis(50));
let locked_x = x.lock().unwrap();
dbg!(*locked_x);
});
thread::spawn(move || {
x2.lock().unwrap()[1] = 42;
});
sleep(Duration::from_millis(100));
}
[src\main.rs:14] *locked_x = [
1,
42,
3,
]
评论
聊天中的讨论摘要。
小说当我谈论“闭包”时,我想到的是任何实现 .为了简洁起见,我将使用这个特定的类型类,因为它们最常用于以下上下文。FnOnce() -> T
的 API 要求生成的线程的寿命可能比生成它的线程长(请注意,这是一个设计选择,并且有不同的 API 不需要这样做,例如 std::thread::scope)。为此,传递给新线程的闭包无法捕获生存期与第一个线程绑定的任何引用。换句话说,它只能保存引用。std::thread::spawn
'static
rust 的特别之处在于它在类型级别强制执行这一点。 具有以下签名:std::thread::spawn
pub fn spawn<F, T>(f: F) -> JoinHandle<T>where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
在类型级别强制传递给的闭包和它返回的值都是 。spawn
'static
重要的是,rust 对待功能的方式与对待任何其他功能的方式不同。事件,如果编译器可以推断新线程不会比主线程生存,则此类型级 API 仍要求所有闭包为 .main
'static
此外,错误消息“闭包可能比当前函数寿命更长”的措辞可能有点误导。闭包不会比函数更持久。它们只是值,在其作用域内有效,就像所有其他值一样。在这种情况下,重要的是哪个线程可以比哪个线程活得更久。由于允许生成的线程比调用它的线程寿命长,因此必须对调用方施加空间限制才能确保安全。std::thread::spawn
评论
main
spawn
'static
x
&'static
&x
&&'static