当使用std::thread
并发时,我对如何管理对对象的引用的生存期感到困惑.
我想我理解I/Rust想要解决的核心问题,那就是如果我向一个函数传递对一个对象的引用,并且该函数产生使用该引用的线程,我需要确保原始数据位将至少与我产生的线程一样长.我读了Lukas Kalbertodt对this question的回答,我知道我可以用std::thread::scope
来做到这一点.我附上了一个简单的例子,如下:
use std::thread;
fn main() {
let owned_string: String = String::from("OWNED DATA");
foo(&owned_string);
}
fn foo(data: &str) {
thread::scope( |s| {
s.spawn(move || {
println!("I am doing something in the first thread!");
println!("I have the data, look: {}", &data);
});
s.spawn(move || {
println!("I am doing something in the second thread!");
println!("I have the data, too, look: {}", &data);
});
});
}
这将打印如下内容
I am doing something in the first thread!
I have the data, look: OWNED DATA
I am doing something in the second thread!
I have the data, too, look: OWNED DATA
需要说明的是,这一切都是可行的,因为使用thread::scope
意味着线程中引用的使用完全包含在定义的作用域内.当作用域结束时,即函数返回之前,引用将被删除(我认为).在函数返回之前,数据本身不能被删除,所以总的来说,程序是内存安全的,因为没有办法让引用挂起.
相比之下,如果没有thread::scope
,但线程只是生成的,那么函数可能会在线程结束之前以某种方式返回,因此引用的数据可能会超出范围,导致引用悬而未决.
我不明白的是,为什么我不能在函数中使用handle.join()
来实现相同的功能.例如:
fn bar(data: &str) {
let mut handles = Vec::new();
handles.push(thread::spawn(move || {
println!("I am doing something in the first thread!");
println!("I have the data, look: {}", &data);
}));
handles.push(thread::spawn(move || {
println!("I am doing something in the second thread!");
println!("I have the data, too, look: {}", &data);
}));
for handle in handles {
handle.join();
}
}
这不能编译,编译器告诉我:
error[E0521]: borrowed data escapes outside of function
--> src/main.rs:24:18
|
22 | fn bar(data: &str) {
| ---- - let's call the lifetime of this reference `'1`
| |
| `data` is a reference that is only valid in the function body
23 | let mut handles = Vec::new();
24 | handles.push(thread::spawn(move || {
| __________________^
25 | | println!("I am doing something in the first thread!");
26 | | println!("I have the data, look: {}", &data);
27 | | }));
| | ^
| | |
| |______`data` escapes the function body here
| argument requires that `'1` must outlive `'static`
For more information about this error, try `rustc --explain E0521`.
原则上,我明白它在说什么(我想),但我也明白,当我调用循环对句柄执行handle.join()
时,它会阻塞,直到所有线程都完成.因此,在所有线程都完成并且引用可以安全删除之前,函数不可能返回.
有人能解释一下为什么第二个例子不是内存安全的吗?或者,这是Rust编译器过于保守的一个例子吗?