这是我在学习 Rust 和遵循Programming Rust时所做的一个实验。
这是操场上代码的链接。
我有一个结构(Thing
)和一些内部状态(xs
)。应该使用Thing::new
创建一个Thing
,然后start
ed,之后用户应该选择调用其他一些函数,如get_xs
。
但!start
spawn
2 个线程,它们调用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
方法的原型:
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();
}
操场。此时程序将编译并运行(尽管worker处于无限循环中,因此操场将在超时后终止进程)。