concurrency - 共享 self 引用时无法推断出合适的生命周期

标签 concurrency rust

这是我在学习 Rust 和遵循Programming Rust 时所做的实验。

这是一个 link to the code in the playground .

我有一个结构 (Thing) 和一些内部状态 (xs)。 Thing 应该使用 Thing::new 创建,然后 started,之后用户应该选择调用一些其他函数,比如 get_xs.

但是!在 start 中,2 个线程被 spawned 调用 Thing 实例上的其他方法,这些方法可能会改变其内部状态(例如,将元素添加到 xs),因此他们需要对 self 的引用(因此是 Arc)。然而,这会导致一生的冲突:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/main.rs:18:30
   |
18 |         let self1 = Arc::new(self);
   |                              ^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined 
on the method body at 17:5...
  --> src/main.rs:17:5
   |
17 | /     fn start(&self) -> io::Result<Vec<JoinHandle<()>>> {
18 | |         let self1 = Arc::new(self);
19 | |         let self2 = self1.clone();
20 | |
...  |
33 | |         Ok(vec![handle1, handle2])
34 | |     }
   | |_____^
note: ...so that expression is assignable (expected &Thing, found &Thing)
  --> src/main.rs:18:30
   |
18 |         let self1 = Arc::new(self);
   |                              ^^^^
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that the type `[closure@src/main.rs:23:20: 25:14 
self1:std::sync::Arc<&Thing>]` will meet its required lifetime bounds
  --> src/main.rs:23:14
   |
23 |             .spawn(move || loop {
   |              ^^^^^

有没有一种方法可以在运行 start 之后将 thing 的所有权归还给正在使用它的代码?

use std::io;
use std::sync::{Arc, LockResult, RwLock, RwLockReadGuard};
use std::thread::{Builder, JoinHandle};

struct Thing {
    xs: RwLock<Vec<String>>
}

impl Thing {

    fn new() -> Thing {
        Thing {
            xs: RwLock::new(Vec::new()),
        }
    }

    fn start(&self) -> io::Result<Vec<JoinHandle<()>>> {
        let self1 = Arc::new(self);
        let self2 = self1.clone();

        let handle1 = Builder::new()
            .name("thread1".to_owned())
            .spawn(move || loop {
                 self1.do_within_thread1();
            })?;

        let handle2 = Builder::new()
            .name("thread2".to_owned())
            .spawn(move || loop {
                self2.do_within_thread2();
            })?;

        Ok(vec![handle1, handle2])
    }

    fn get_xs(&self) -> LockResult<RwLockReadGuard<Vec<String>>> {
        return self.xs.read();
    }

    fn do_within_thread1(&self) {
        // read and potentially mutate self.xs
    }

    fn do_within_thread2(&self) {
        // read and potentially mutate self.xs
    }
}

fn main() {
    let thing = Thing::new();
    let handles = match thing.start() {
        Ok(hs) => hs,
        _ => panic!("Error"),
    };

    thing.get_xs();

    for handle in handles {
        handle.join();
    }
}

最佳答案

错误消息说传递给 Arc 的值必须住 'static生命周期。这是因为产生一个线程,无论是 std::thread::spawn std::thread::Builder ,要求传递的闭包在这个生命周期内存在,从而使线程能够在生成线程的范围之外“自由地存在”。

让我们展开 start 的原型(prototype)方法:

fn start<'a>(&'a self: &'a Thing) -> io::Result<Vec<JoinHandle<()>>> { ... }

尝试放置 &'a self进入 Arc创建一个 Arc<&'a Thing> ,这仍然受限于生命周期 'a ,因此不能移动到需要比这更长寿的闭包。因为我们不能搬出去&self或者,解决方案是不使用 &self对于这种方法。相反,我们可以制作 start接受 Arc直接:

fn start(thing: Arc<Self>) -> io::Result<Vec<JoinHandle<()>>> {
    let self1 = thing.clone();
    let self2 = thing;

    let handle1 = Builder::new()
        .name("thread1".to_owned())
        .spawn(move || loop {
             self1.do_within_thread1();
        })?;

    let handle2 = Builder::new()
        .name("thread2".to_owned())
        .spawn(move || loop {
            self2.do_within_thread2();
        })?;

    Ok(vec![handle1, handle2])
}

并在消费者范围内传递引用计数指针:

let thing = Arc::new(Thing::new());
let handles = Thing::start(thing.clone()).unwrap_or_else(|_| panic!("Error"));

thing.get_xs().unwrap();

for handle in handles {
    handle.join().unwrap();
}

Playground .此时程序会编译运行(虽然worker处于死循环中,所以playground会在超时后kill进程)。

关于concurrency - 共享 self 引用时无法推断出合适的生命周期,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49525639/

相关文章:

java - volatile jls 示例

concurrency - 使用共享 map 的不错的惯用方式

Node.js/Express 和并行队列

java - LibGDX 中的并发 Http 请求

javascript - 使用rust+webassembly进行web开发,如何解决wasm与js交互的额外开销

asynchronous - 如何异步浏览目录及其子目录?

recursion - 有没有办法优化这段代码,使其不会溢出堆栈?

c++ - 我可以从 Rust 代码调用 C 或 C++ 函数吗?

rust - 为什么 Iterator::take_while 取得迭代器的所有权?

concurrency - 用于并发 NDKernal 启动的 OpenCL 多命令队列