如果当主函数结束时整个过程结束时,闭包如何比主函数寿命长?

How can a closure outlive the main function if the entire process ends when main function ends?

提问人:Fajela Tajkiya 提问时间:9/14/2023 最后编辑:Aleksander KrauzeFajela Tajkiya 更新时间:9/14/2023 访问量:49

问:

我有以下代码:

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 函数结束时,整个过程也结束。这意味着从此进程生成的所有线程也会终止,无论它们是否已完成执行。那么,闭合如何才能比主要功能更持久呢?

Rust Closures 所有权

评论

2赞 Aleksander Krauze 9/14/2023
Rust 看不出和任何其他函数之间的区别。传递给的闭包必须是 。mainspawn'static
1赞 Aleksander Krauze 9/14/2023
请注意,即使 是 ,也是对静态引用 () 的引用,因此具有非静态生存期。x&'static&x&&'static
0赞 Aleksander Krauze 9/14/2023
当您想告诉编译器在当前线程存在之前必须联接生成的线程时,可以使用 std::thread::scope。这应该可以解决您的问题。
0赞 Fajela Tajkiya 9/14/2023
是的,但你没有回答我的问题。
1赞 Fajela Tajkiya 9/14/2023
感谢您的评论!我想我明白你的意思。

答:

3赞 Finomnis 9/14/2023 #1

线程在 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_xdrop()&&mut_xdrop()

出于这样的原因,该函数被简单地视为正常函数,并且所有正常的借用规则都适用。main()


这些是关于为什么被视为普通函数的一些通用词,但现在让我们谈谈您的代码。main()

看来您试图通过 ing 变量来克服这个问题。这绝对是一种将变量的生存期延长到 结束之后的可能方法,但你犯了一个错误。数组本身是 ,但变量不是。当你在线程中这样做时,你引用的不是数组,而是变量 ,并且位于 中。Box::leakmain()&'staticx&xxxmain()

您需要将引用本身移动到线程中。因为这与将自身移动到线程中有什么不同并不明显,所以我将创建第二个线程进行演示: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&Copylet 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,
]

评论

0赞 Fajela Tajkiya 9/14/2023
精彩的解释和例子!谢谢你的回答!
1赞 Aleksander Krauze 9/14/2023 #2

聊天中的讨论摘要。

小说当我谈论“闭包”时,我想到的是任何实现 .为了简洁起见,我将使用这个特定的类型类,因为它们最常用于以下上下文。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