我正在考虑将拥有一些线程安全值的闭包传递给生成的线程的可能性。然后该线程将能够仅通过知道签名来调用某些东西,而其内容对它来说是不透明的。
我写了一个简单的测试,令人惊讶的是,它成功了。为了使闭包可以在线程内调用,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
如此),而是F: 'static
意味着“此值必须能够存在到程序结束(但不一定必须真正存在到那时)”。实际上,这意味着 里面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
对下面的值有引用一样;唯一的区别是当代码中不再可访问时它会自动删除。