多线程处理元素时函数调用生命周期参数问题
咱们先拆解一下你遇到的问题,再一步步把它搞定。
为啥会出现这个错误?
你看到的error[E0495]本质上是Rust的安全机制在兜底:thread::spawn创建的线程是不受当前函数控制的——它完全可能在run方法执行完毕后还继续运行。而你用self.delegates.iter()拿到的是&Delegate类型的引用,这些引用的生命周期和run方法里的&self绑定(也就是报错里提到的“匿名生命周期#1”),只能在run方法内部有效。
Rust担心如果线程在run结束后还在跑,那这些引用就会变成“悬垂引用”(指向已经被销毁的对象),所以它要求线程闭包里捕获的变量必须是'static生命周期的(简单说就是能存活到程序结束,或者至少能保证在线程运行期间一直有效)。这就导致了冲突:引用的生命周期只能到run结束,可线程要求它得是'static,编译器自然没法推断出合适的生命周期参数,于是就报错了。
你提到的“函数调用中的生命周期参数”,指的是iter()方法返回的迭代器里的引用的生命周期——iter()的签名隐含了它返回的引用和调用者(self.delegates)的生命周期绑定,而这个绑定和线程的'static要求直接冲突了。
两种可行的修复方案
方案1:把Delegate的所有权转移给线程
如果你的Conference不需要在run之后继续持有delegates的所有权,那最简单的办法就是用into_iter()代替iter(),把每个Delegate的所有权转移到线程里,这样就不需要引用了,自然也没生命周期问题。
修改后的run方法代码如下:
fn run(mut self) { let (tx, rx) = mpsc::channel(); // 用into_iter转移所有权,迭代的是Delegate而非&Delegate for d in self.delegates.into_iter() { let txd = mpsc::Sender::clone(&tx); thread::spawn(move || { let presentation = d.present("a resolution"); txd.send(presentation).unwrap(); }); } // 手动drop tx,这样当所有线程都发送完消息后,rx会知道没有新消息了,循环会结束 drop(tx); for received in rx { println!("{}", received); } }
注意这里run方法的参数从&self改成了mut self(其实如果不需要修改的话,self就行),因为我们要把delegates的所有权移出去。
方案2:用Arc共享所有权(保留Conference的所有权)
如果你需要Conference在run之后还能继续使用delegates,那可以用Arc(原子引用计数指针)来实现线程安全的共享所有权。Arc允许多个线程同时持有同一个对象的引用,而且引用计数会保证对象在最后一个Arc被销毁前不会被释放。
修改步骤如下:
- 导入
Arc:use std::sync::Arc; - 修改
Conference的结构,把Vec<Delegate>改成Vec<Arc<Delegate>> - 在
new方法里把每个Delegate包装成Arc - 在
run方法里克隆Arc给每个线程
修改后的完整相关代码:
use std::sync::Arc; // ... 其他导入保持不变 struct Conference { delegates: Vec<Arc<Delegate>>, } impl Conference { fn new(delegates: Vec<Delegate>) -> Conference { Conference { // 把每个Delegate转换成Arc<Delegate> delegates: delegates.into_iter().map(Arc::new).collect(), } } fn run(&self) { let (tx, rx) = mpsc::channel(); for d in &self.delegates { let txd = mpsc::Sender::clone(&tx); // 克隆Arc,增加引用计数 let d_clone = Arc::clone(d); thread::spawn(move || { let presentation = d_clone.present("a resolution"); txd.send(presentation).unwrap(); }); } drop(tx); for received in rx { println!("{}", received); } } }
这样每个线程拿到的Arc<Delegate>是'static的,满足thread::spawn的要求,同时Conference也依然持有delegates的所有权。
内容的提问来源于stack exchange,提问作者Melvin Sowah

