如何跨线程使用`Vec<&'a dyn Trait>`(特征对象引用的集合)?

问题描述 投票:0回答:2

我正在尝试跨线程使用

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
方法的生命周期?

rust concurrency lifetime
2个回答
0
投票

感谢@cafce25评论,我能够找到对我有用的答案。

对于任意线程,它们必须是

'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
    }
}

0
投票

可以使用更简单的代码重现类似的错误:

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}"); }); }); }

的调用者同时运行。原则上,您可以为每个可启动任务生成一个作用域线程,以使任务彼此并发。

    

© www.soinside.com 2019 - 2024. All rights reserved.