如何跨线程安全共享带生命周期参数且实现dyn trait的结构体?
如何跨线程安全共享带生命周期参数且实现dyn trait的结构体?
以下是我的代码:
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, mpsc, Mutex}; use std::thread; trait Draw { fn draw(&self); } #[derive(Default)] struct Button { } impl Draw for Button { fn draw(&self) { println!("draw button"); } } #[derive(Default)] struct SelectionBox { } impl Draw for SelectionBox { fn draw(&self) { println!("draw selection box"); } } #[derive(Default)] struct TextField { } impl Draw for TextField { fn draw(&self) { println!("draw text field"); } } pub struct RunningThreadInterface<T> { pub instance: Arc<T>, pub thread_join_handle: thread::JoinHandle<()>, } pub trait StartThread<T> { fn start(self, thread_id: String) -> RunningThreadInterface<T>; fn run(&self); } pub trait TerminateThread { fn stop(&mut self); fn wait(self); } struct Screen<'a> { widgets: Mutex<Vec<&'a (dyn Draw + Send + Sync)>>, rx: Mutex<mpsc::Receiver<String>>, terminate_flag: AtomicBool, } impl<'a> Screen<'a> { fn new(rx: mpsc::Receiver<String>) -> Screen<'a> { Screen { widgets: Mutex::new(Vec::new()), rx: Mutex::new(rx), terminate_flag: AtomicBool::new(false), } } fn add(&mut self, widget: &'a (dyn Draw + Send + Sync)) { self.widgets.lock().unwrap().push(widget); } fn draw_widgets(&self) { for widget in &*self.widgets.lock().unwrap() { widget.draw(); } } } impl<'a> StartThread<Screen<'a>> for Screen<'a> { fn start(self, thread_id: String) -> RunningThreadInterface<Screen<'a>> { let screen = Arc::new(self); RunningThreadInterface { instance: Arc::clone(&screen), thread_join_handle: thread::Builder::new().name(thread_id).spawn(move || screen.run()).ok().unwrap(), } } fn run(&self) { while !self.terminate_flag.load(Ordering::SeqCst) { self.rx.lock().unwrap().recv().unwrap(); } } } impl<'a> TerminateThread for RunningThreadInterface<Screen<'a>> { fn stop(&mut self) { self.instance.terminate_flag.store(true, Ordering::SeqCst); } fn wait(self) { self.thread_join_handle.join(); } } fn main() { let button: Button = Default::default(); let selection_box: SelectionBox = Default::default(); let text_field: TextField = Default::default(); let (_tx, rx) = mpsc::channel(); let mut screen = Screen::new(rx); screen.add(&button); screen.add(&selection_box); screen.add(&text_field); screen.draw_widgets(); println!(""); button.draw(); selection_box.draw(); text_field.draw(); }
错误信息:
error[E0521]: borrowed data escapes outside of method --> src/main.rs:90:33 | 85 | impl<'a> StartThread<Screen<'a>> for Screen<'a> { | -- lifetime `'a` defined here 86 | fn start(self, thread_id: String) -> RunningThreadInterface<Screen<'a>> { | ---- `self` is a reference that is only valid in the method body ... 90 | thread_join_handle: thread::Builder::new().name(thread_id).spawn(move || screen.run()).ok().unwrap(), | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | `self` escapes the method body here | argument requires that `'a` must outlive `'static` | = note: requirement occurs because of the type `Screen<'_>`, which makes the generic argument `'_` invariant = note: the struct `Screen<'a>` is invariant over the parameter `'a` = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance For more information about this error, try `rustc --explain E0521`.
注意:我无法修改StartThread和TerminateThread trait的现有语法,但可以接受设计优化建议。
解决方案
错误核心原因是:Screen<'a>存储了对widget的引用&'a dyn Draw,而线程创建时要求捕获的变量必须满足'static生命周期(线程的生命周期不受当前函数上下文约束,无法保证引用的有效性),但'a无法满足'static要求,导致引用逃逸。
优化方案是将引用改为拥有所有权的智能指针,用Arc<dyn Draw + Send + Sync>代替&'a dyn Draw,这样Screen不再需要生命周期参数,线程可以安全持有Arc共享所有权:
修改后的代码如下:
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, mpsc, Mutex}; use std::thread; trait Draw { fn draw(&self); } #[derive(Default)] struct Button {} impl Draw for Button { fn draw(&self) { println!("draw button"); } } #[derive(Default)] struct SelectionBox {} impl Draw for SelectionBox { fn draw(&self) { println!("draw selection box"); } } #[derive(Default)] struct TextField {} impl Draw for TextField { fn draw(&self) { println!("draw text field"); } } pub struct RunningThreadInterface<T> { pub instance: Arc<T>, pub thread_join_handle: thread::JoinHandle<()>, } pub trait StartThread<T> { fn start(self, thread_id: String) -> RunningThreadInterface<T>; fn run(&self); } pub trait TerminateThread { fn stop(&mut self); fn wait(self); } // 移除生命周期参数,改用Arc存储widget struct Screen { widgets: Mutex<Vec<Arc<dyn Draw + Send + Sync>>>, rx: Mutex<mpsc::Receiver<String>>, terminate_flag: AtomicBool, } impl Screen { fn new(rx: mpsc::Receiver<String>) -> Screen { Screen { widgets: Mutex::new(Vec::new()), rx: Mutex::new(rx), terminate_flag: AtomicBool::new(false), } } // 接收Arc<dyn Draw>类型的参数 fn add(&mut self, widget: Arc<dyn Draw + Send + Sync>) { self.widgets.lock().unwrap().push(widget); } fn draw_widgets(&self) { for widget in &*self.widgets.lock().unwrap() { widget.draw(); } } } // 实现StartThread时不再需要生命周期参数 impl StartThread<Screen> for Screen { fn start(self, thread_id: String) -> RunningThreadInterface<Screen> { let screen = Arc::new(self); RunningThreadInterface { instance: Arc::clone(&screen), thread_join_handle: thread::Builder::new() .name(thread_id) .spawn(move || screen.run()) .ok() .unwrap(), } } fn run(&self) { while !self.terminate_flag.load(Ordering::SeqCst) { // 忽略接收错误(比如发送端关闭),避免线程panic if self.rx.lock().unwrap().recv().is_err() { break; } } } } impl TerminateThread for RunningThreadInterface<Screen> { fn stop(&mut self) { self.instance.terminate_flag.store(true, Ordering::SeqCst); } fn wait(self) { // 处理join可能的panic let _ = self.thread_join_handle.join(); } } fn main() { // 将widget包装成Arc,共享所有权 let button = Arc::new(Button::default()); let selection_box = Arc::new(SelectionBox::default()); let text_field = Arc::new(TextField::default()); let (tx, rx) = mpsc::channel(); let mut screen = Screen::new(rx); screen.add(button.clone()); screen.add(selection_box.clone()); screen.add(text_field.clone()); screen.draw_widgets(); println!(""); button.draw(); selection_box.draw(); text_field.draw(); // 启动线程示例 let mut thread_interface = screen.start("screen_thread".to_string()); // 发送消息触发线程循环 tx.send("test".to_string()).unwrap(); // 停止线程并等待结束 thread_interface.stop(); thread_interface.wait(); }
关键修改点
- 移除
Screen的生命周期参数'a,将widgets的类型从Mutex<Vec<&'a (dyn Draw + Send + Sync)>>改为Mutex<Vec<Arc<dyn Draw + Send + Sync>>>,用拥有所有权的Arc替代引用,保证线程安全共享。 - 修改
Screen::add方法的参数类型为Arc<dyn Draw + Send + Sync>,接收智能指针而非引用。 - 在
main中,将所有widget包装为Arc,通过clone()共享所有权给Screen和当前上下文。 - 优化
run和wait方法的错误处理,避免不必要的panic。
内容的提问来源于stack exchange,提问作者Harry
相关产品推荐
相关产品推荐

