小能豆

如何将对堆栈变量的引用传递给线程?

rust

我正在编写一个 WebSocket 服务器,Web 客户端连接到该服务器,与多线程计算机 AI 下棋。WebSocket 服务器想要将一个Logger对象传递到 AI 代码中。该Logger对象将把日志行从 AI 传输到 Web 客户端。必须Logger包含对客户端连接的引用。

我对生命周期如何与线程交互感到困惑。我已经用Wrapper类型参数化的结构重现了该问题。该run_thread函数尝试解开该值并记录它。

use std::fmt::Debug;
use std::thread;

struct Wrapper<T: Debug> {
    val: T,
}

fn run_thread<T: Debug>(wrapper: Wrapper<T>) {
    let thr = thread::spawn(move || {
        println!("{:?}", wrapper.val);
    });

    thr.join();
}

fn main() {
    run_thread(Wrapper::<i32> { val: -1 });
}

wrapper参数存在于堆栈中,并且其生命周期不会超出 的run_thread堆栈帧,即使线程将在堆栈帧结束之前加入。我可以从堆栈中复制该值:

use std::fmt::Debug;
use std::thread;

struct Wrapper<T: Debug + Send> {
    val: T,
}

fn run_thread<T: Debug + Send + 'static>(wrapper: Wrapper<T>) {
    let thr = thread::spawn(move || {
        println!("{:?}", wrapper.val);
    });

    thr.join();
}

fn main() {
    run_thread(Wrapper::<i32> { val: -1 });
}

T如果是对我不想复制的大对象的引用,则这将不起作用:

use std::fmt::Debug;
use std::thread;

struct Wrapper<T: Debug + Send> {
    val: T,
}

fn run_thread<T: Debug + Send + 'static>(wrapper: Wrapper<T>) {
    let thr = thread::spawn(move || {
        println!("{:?}", wrapper.val);
    });

    thr.join();
}

fn main() {
    let mut v = Vec::new();
    for i in 0..1000 {
        v.push(i);
    }

    run_thread(Wrapper { val: &v });
}

结果是:

error: `v` does not live long enough
  --> src/main.rs:22:32
   |
22 |     run_thread(Wrapper { val: &v });
   |                                ^ does not live long enough
23 | }
   | - borrowed value only lives until here
   |
   = note: borrowed value must be valid for the static lifetime...

我能想到的唯一解决方案是使用Arc.

use std::fmt::Debug;
use std::sync::Arc;
use std::thread;

struct Wrapper<T: Debug + Send + Sync + 'static> {
    arc_val: Arc<T>,
}

fn run_thread<T: Debug + Send + Sync + 'static>(wrapper: &Wrapper<T>) {
    let arc_val = wrapper.arc_val.clone();
    let thr = thread::spawn(move || {
        println!("{:?}", *arc_val);
    });

    thr.join();
}

fn main() {
    let mut v = Vec::new();
    for i in 0..1000 {
        v.push(i);
    }

    let w = Wrapper { arc_val: Arc::new(v) };
    run_thread(&w);

    println!("{}", (*w.arc_val)[0]);
}

在我的真实程序中,看来Logger和 连接对象都必须放置在Arc包装器中。Arc当代码在库内部进行并行化时,客户端需要将连接装箱,这似乎很烦人。这尤其令人烦恼,因为连接的生命周期保证大于工作线程的生命周期。

我错过了什么吗?


阅读 71

收藏
2024-05-21

共1个答案

小能豆

您所遇到的问题是由于线程需要 'static 生命周期的限制所导致的。线程必须保证其引用的数据在整个线程的生命周期内有效,因此对于传递给线程的数据,其生命周期必须至少与线程的生命周期一样长,即 'static 生命周期。

您的第一个解决方案是将数据复制到堆上,并传递给线程,这样可以确保数据在整个线程生命周期内有效,但这并不适用于大对象。

您的第二个解决方案是使用 Arc 来共享数据,这是一个更好的解决方案。Arc 允许多个线程共享同一份数据,而不需要担心生命周期的问题,并且不会导致数据的不必要复制。

虽然在使用 Arc 时需要在堆上分配内存,并且稍微增加了开销,但在需要共享数据并在多个线程之间传递数据时,这是一种非常常见和合理的做法。只要确保每个线程都持有 Arc 的克隆,这种开销就是可控的。

因此,您的方法是正确的,并且在 Rust 中使用 Arc 来处理多线程共享数据是一种常见且有效的做法。

2024-05-21