这是我在学习 Rust 和遵循Programming Rust 时所做的实验。
这是一个 link to the code in the playground .
我有一个结构 (Thing
) 和一些内部状态 (xs
)。 Thing
应该使用 Thing::new
创建,然后 start
ed,之后用户应该选择调用一些其他函数,比如 get_xs
.
但是!在 start
中,2 个线程被 spawn
ed 调用 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/