我正在考虑将拥有一些线程安全值的闭包传递给生成的线程的可能性。然后,线程将能够调用只知道签名的东西,而它的内容对其来说是不透明的。
我写了一个简单的测试,令人惊讶的是,它有效。为了使闭包可以在线程内调用,Rust 编译器建议为其添加
std::marker::Send
和 'static
约束。第一个适用是有道理的——闭包捕获的所有值都是Send
,但是被认为满足的静态生命周期要求让我感到困惑,因为我假设编译器会考虑闭包及其拥有的值具有特定的非-静态寿命。
我想了解为什么。这是代码:
use std::sync::{mpsc::{Receiver, Sender}, Arc, Mutex};
fn main() {
let tx = start_thread();
// can also run in a loop with a sleep in between or whatever
let _ = tx.send(String::from("Something"));
}
fn start_thread() -> Sender<String> {
// Something created on the main thread that we pass to the spawned thread
// inside the closure -- the spawned thread has no direct knowledge of it
let closure_owned_value: Arc<Mutex<f32>> = Arc::new(Mutex::new(42.0));
// A closure we want to be called by the spawned thread,
// but not defined there -- this is considered 'static somehow?
let on_thread_callback = move |msg: String| {
println!("Got back message from thread: {:?}, owned value: {:?}", msg, closure_owned_value);
};
let (tx, rx) = std::sync::mpsc::channel();
spawn_thread_with_callback(rx, on_thread_callback);
tx
}
fn spawn_thread_with_callback<F>(
rx: Receiver<String>,
callback: F
) -> std::thread::JoinHandle<()>
where
F: Fn(String) -> () + std::marker::Send + 'static
{
std::thread::spawn(move || {
/* Run an infinite loop as long as channel is open. */
while let Ok(message) = rx.recv() {
println!("Thread received message: {:?}", message);
callback(String::from("Hello back from thread!"));
}
})
}
您所指的
'static
生命周期可能有点误导。
在函数声明中,有
F: Fn(String) -> () + std::marker::Send + 'static
。为了简单起见,我们可以考虑 F: 'static
是什么意思。
请注意,您不会通过
callback
引用传递 'static
(您拥有 callback: F
而不是 callback: &'static F
)。这两者的含义完全不同:&'static F
不是表达“这个reference必须存活到程序结束”(
F: 'static
确实如此),而是“这个value必须能够存活”直到计划结束(但不一定要真正活到那时)”。实际上,这意味着在 F
内部不能有任何非 'static
引用。这意味着 i32
是 'static
,String
是 'static
,但 struct Foo<'a> { bar: &'a str }
(其中 a
不是 static
)不是。
现在,考虑一下您的代码中发生了什么。您创建一个通过移动捕获
Arc<Mutex<f32>>
的闭包。因此,它不会在 start_thread
范围结束时被解构;相反,只要解构闭包,它就会被解构。另请注意,创建 closure_owned_value
后不能使用 on_thread_callback
— 因为您通过移动传递了 Arc
,所以它在作用域中不再可用,尝试使用它会导致编译错误(正是因为它的所有权已被移动)。 Arc
可以很容易地存活到程序结束——它是一个底层的引用计数引用,其行为就像你有一个对下面的值的 'static
引用;唯一的区别是,当代码中不再可达时,它会自动删除。