我正在尝试跨线程使用
Vec<&'a dyn Trait>
(对特征对象的引用的集合)。这是我的代码,我认为它的注释是不言自明的:
use std::{
sync::{Arc, Mutex},
thread::{self, JoinHandle},
};
// This is basis for some trait objects
trait Startable {
fn start(&self);
}
// A runner has a collection of references to these trait objects
// - ...which is behind a Mutex, so it could be concurrently accessed
// - ...and aslo behind an Arc, so it could be safely cloned and sent to other threads.
struct Runner<'a> {
startables: Arc<Mutex<Vec<&'a (dyn Startable + Sync)>>>,
}
impl<'a> Runner<'a> {
fn start_all(&mut self) -> JoinHandle<()> {
// Get a copy so it can be moved to other thread.
let startables = self.startables.clone(); // ERROR!
// Runner starts a new thread where it expects to use the
// collection of references to these trait objects.
let handle = thread::spawn(move || {
let startables = startables.lock().unwrap();
// Do something with the startables, e.g.:
for startable in startables.iter() {
startable.start();
}
});
handle
}
}
fn main() {
let mut runner = Runner {
startables: Arc::new(Mutex::new(vec![/* trait objects */])),
};
let handle = runner.start_all();
let _ = handle.join();
}
这会产生以下错误:
error[E0521]: borrowed data escapes outside of method
--> src/main.rs:21:26
|
18 | impl<'a> Runner<'a> {
| -- lifetime `'a` defined here
19 | fn start_all(&mut self) -> JoinHandle<()> {
| --------- `self` is a reference that is only valid in the method body
20 | // Get a copy so it can be moved to other thread.
21 | let startables = self.startables.clone();
| ^^^^^^^^^^^^^^^^^^^^^^^
| |
| `self` escapes the method body here
| argument requires that `'a` must outlive `'static`
|
= note: requirement occurs because of the type `Mutex<Vec<&dyn Startable + Sync>>`, which makes the generic argument `Vec<&dyn Startable + Sync>` invariant
= note: the struct `Mutex<T>` is invariant over the parameter `T`
我不太明白为什么
T
中的Mutex<T>
不变会成为一个问题。因为,如果集合是 Vec<Box<(dyn Startable + Sync + Send)>>
,则编译不会有问题:
//... other definitions as before
struct Runner {
// Only change is in this line:
startables: Arc<Mutex<Vec<Box<(dyn Startable + Sync + Send)>>>>,
}
impl Runner {
fn start_all(&mut self) -> JoinHandle<()> {
let startables = self.startables.clone(); // WORKS!
let start_thread = thread::spawn(move || {
let startables = startables.lock().unwrap();
// ...
});
start_thread
}
}
但我想跨线程使用对特征对象的引用,而不需要
'static
生命周期。我在这里缺少什么?如何更好地指定生命周期以便原始代码可以编译?或者是否有任何形式的标记可以用来指示集合的生命周期超出了 start_all
方法的生命周期?
对于任意线程,它们必须是
编译器不知道线程运行多长时间,但引用必须在可以访问它的每个线程的整个运行时有效,因此必须选择允许任意长度的唯一生命周期。'static!
这就是我想到的:
struct Runner {
startables: Arc<Mutex<Vec<Arc<dyn Startable + Send + Sync>>>>,
}
/* rest of the code as before */
至少对于我来说,原子引用计数与引用一样好。
如果您需要集合中的可变项目:
trait Startable {
// Now, start takes a mutable reference
fn start(&mut self);
}
struct Runner {
// Each item in the collection is behind a Mutex
startables: Arc<Mutex<Vec<Arc<Mutex<dyn Startable + Send + Sync>>>>>,
}
impl Runner {
fn start_all(&mut self) -> JoinHandle<()> {
let startables = self.startables.clone();
let start_thread = thread::spawn(move || {
let startables = startables.lock().unwrap();
for startable in startables.iter() {
// You need to lock the Mutex in order to mutate items
startable.lock().unwrap().start();
}
});
start_thread
}
}
可以使用更简单的代码重现类似的错误:
fn start_all(reference: &u8) {
std::thread::spawn(move || {
println!("{reference}");
});
}
fn main() {
start_all(&5);
}
产生:
--> src/main.rs:2:5
|
1 | fn start_all(reference: &u8) {
| --------- - let's call the lifetime of this reference `'1`
| |
| `reference` is a reference that is only valid in the function body
2 | / std::thread::spawn(move || {
3 | | println!("{reference}");
4 | | });
| | ^
| | |
| |______`reference` escapes the function body here
| argument requires that `'1` must outlive `'static`
For more information about this error, try `rustc --explain E0521`.
因此,(尽管有问题)
dyn
、Vec
和(尽管有原始错误消息)Mutex
和(in)方差不一定相关。
使此代码编译的唯一生命周期是
reference
,因为任何其他生命周期都可能在生成的线程运行之前过期。使用 'static
代替
Box<_>
也可以,因为 &_
传达所有权,直到在原始情况下,包含它的 Box
的所有弧引用都被删除为止。这些弧引用之一由生成的线程传输。为了完整起见,请注意使用作用域线程可以进行编译:
Arc
但是,这些线程会立即加入,因此它们不会与
fn start_all(reference: &u8) {
std::thread::scope(|s| {
s.spawn(|| {
println!("{reference}");
});
});
}
的调用者同时运行。原则上,您可以为每个可启动任务生成一个作用域线程,以使任务彼此并发。