借用的价值在 Arc、thread 和 channel 中存在的时间不够长

问题描述

我的代码的目标是在线程中发送带有通道 (Data) 的自定义 std::sync::mpsc::channelData 实例由 Manager 创建并在 Manager 上有引用。

use std::sync::mpsc;
use std::sync::Arc;
use std::thread;

struct Manager {}

struct Data<'a> {
    manager: &'a Manager,}

impl Manager {
    fn new() -> Self {
        Self {}
    }

    fn make(&self) -> Data {
        Data { manager: self }
    }
}

unsafe impl Send for Manager {}

fn main() {
    let manager = Arc::new(Manager::new());
    let (tx,_rx) = mpsc::channel();
    let mut children = Vec::new();

    for id in 0..2 {        
        let thread_manager = Arc::clone(&manager);
        let thread_tx = tx.clone();

        let child = thread::spawn(move || {
            let msg = thread_manager.make();
            thread_tx.send(msg).unwrap();
            println!("thread {} finished",id);
        });
        children.push(child);
    }
}

playground

我有一个错误

error[E0597]: `thread_manager` does not live long enough
  --> src/main.rs:36:23
   |
33 |         let thread_tx = tx.clone();
   |             --------- lifetime `'1` appears in the type of `thread_tx`
...
36 |             let msg = thread_manager.make();
   |                       ^^^^^^^^^^^^^^ borrowed value does not live long enough
37 |             thread_tx.send(msg).unwrap();
   |             ------------------- argument requires that `thread_manager` is borrowed for `'1`
38 |             println!("thread {} finished",id);
39 |         });
   |         - `thread_manager` dropped here while still borrowed

但我不明白:

  1. let manager = Arc::new(Manager::new()); 使用引用计数器创建 Manager 的实例
  2. let thread_manager = Arc::clone(&manager); 增加 1 个引用计数器,但它是同一个 Managerinstance
  3. let msg = thread_manager.make(); 创建一个 Data 并引用 Manager

对我来说,msg 可以和 manager 有相同的生命周期。

我哪里错了?

谢谢 艾蒂安

解决方法

对我来说,msg 可以和 manager 有相同的生命周期。

是的。这意味着 msg 的生命周期与封闭函数一样长:

let child = thread::spawn(move || {
    // <— thread_manager moved into and owned by the function
    let msg = thread_manager.make();
    thread_tx.send(msg).unwrap();
    println!("thread {} finished",id);
    // <— thread_manager is dropped here so `msg` becomes invalid
});

不知道何时实际接收/使用消息,但很可能是在线程死亡之后。并且 rustc 不能假设管理器的任何其他实例此时会保持活动状态,因此它必须假设它会留下一个悬空引用。

为什么 msg 不直接使用 Arc<Manager>,甚至是 Weak<Manager>? (尽管后者在这里似乎不是一个好主意)。

您可能可以使用范围线程按照您正在尝试的方式做一些事情(我相信甚至没有Arc),但到目前为止,stdlib 的范围不是由于编译器关注的是 spawn 调用的调用者和内容之间没有生命周期关系,它们完全独立生活,并且可以以任何顺序运行和停止(即使您使用join)。所以关于线程绑定生命周期的推理完全是本地的。