我正在编写一个 WebSocket 服务器,Web 客户端连接到该服务器,与多线程计算机 AI 下棋。WebSocket 服务器想要将一个Logger对象传递到 AI 代码中。该Logger对象将把日志行从 AI 传输到 Web 客户端。必须Logger包含对客户端连接的引用。
Logger
我对生命周期如何与线程交互感到困惑。我已经用Wrapper类型参数化的结构重现了该问题。该run_thread函数尝试解开该值并记录它。
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堆栈帧,即使线程将在堆栈帧结束之前加入。我可以从堆栈中复制该值:
wrapper
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如果是对我不想复制的大对象的引用,则这将不起作用:
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.
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当代码在库内部进行并行化时,客户端需要将连接装箱,这似乎很烦人。这尤其令人烦恼,因为连接的生命周期保证大于工作线程的生命周期。
我错过了什么吗?
您所遇到的问题是由于线程需要 'static 生命周期的限制所导致的。线程必须保证其引用的数据在整个线程的生命周期内有效,因此对于传递给线程的数据,其生命周期必须至少与线程的生命周期一样长,即 'static 生命周期。
'static
您的第一个解决方案是将数据复制到堆上,并传递给线程,这样可以确保数据在整个线程生命周期内有效,但这并不适用于大对象。
您的第二个解决方案是使用 Arc 来共享数据,这是一个更好的解决方案。Arc 允许多个线程共享同一份数据,而不需要担心生命周期的问题,并且不会导致数据的不必要复制。
虽然在使用 Arc 时需要在堆上分配内存,并且稍微增加了开销,但在需要共享数据并在多个线程之间传递数据时,这是一种非常常见和合理的做法。只要确保每个线程都持有 Arc 的克隆,这种开销就是可控的。
因此,您的方法是正确的,并且在 Rust 中使用 Arc 来处理多线程共享数据是一种常见且有效的做法。