我有一个类似于在是否有一种简洁的方法可以生成具有现有数据副本的新线程?中讨论的问题。与链接问题不同,我试图将具有关联生存期的对象移动到新线程中。
直观地说,我要做的是复制所有需要的东西,以便继续计算到新线程并退出旧线程。但是,当试图将克隆的数据(具有生存期的)移动到新线程时,我会得到以下错误:
errorE0759:
data有生命周期'a,但它需要满足'static生命周期需求
我根据引用的问题这里创建了一个可重复的示例。这只是为了说明这个问题。在这里,可以很容易地删除生命周期,但是在我的实际使用中,我想要移动到线程的数据要复杂得多。
有什么简单的方法可以让这件事和锈一起用吗?
发布于 2021-07-18 10:18:28
对标题中的问题的一个限定答案是“是”,但我们不能通过复制非静态引用来做到这一点。这个看似有限的原因是合理的。将所需数据/对象传递到线程闭包中的方法是将它们的所有权(或它们的副本,或表示它们的其他具体对象)传递给闭包。
对于如何使用像pyo3这样的复杂库,可能还不清楚,因为许多API返回引用类型给对象,而不是可以传递给其他线程的具体对象,但是这个库确实提供了将Python /对象传递给其他线程的方法,我将在下面的第二个示例中介绍这一点。
start()函数将需要在与其data参数关联的闭包类型上放置一个'static绑定,因为在它的主体中,start()将这些闭包传递给其他线程。编译器正在努力确保闭包不会保留对线程运行时间超过其父线程的任何引用,这就是为什么它抱怨没有'static保证的原因。
fn start<'a>(data : Vec<Arc<dyn Fn() -> f64 + Send + Sync + 'static>>,
more_data : String)
{
for _ in 1..=4 {
let cloned_data = data.clone();
let cloned_more_data = more_data.clone();
thread::spawn(move || foo(cloned_data, cloned_more_data));
}
}'static绑定与应用于引用的'static生存期不同(data: 'static与&'static data)。在绑定的情况下,它只意味着它应用到的类型不包含任何非静态引用(如果它甚至包含任何引用)。在线程代码中将这种绑定应用于方法参数是非常常见的。
由于这特别适用于pyo3问题空间,我们可以通过将任何此类引用转换为拥有的对象来避免形成包含非静态引用的闭包,然后当运行在另一个线程中的回调需要对它们执行一些操作时,它可以获取GIL并将它们转换回Python引用。
在下面的代码注释中有更多关于这一点的信息。我从pyo3 GitHub自述中获取了一个GitHub,并将它与操场实例中提供的代码结合起来。
在应用此模式时,需要注意的是死锁。线程需要获取GIL,以便使用它们可以访问的Python对象。在本例中,父线程一旦生成新线程,就会在GIL超出作用域时释放它。然后,父线程通过连接其句柄等待子线程完成。
use std::thread;
use std::thread::JoinHandle;
use std::sync::Arc;
use pyo3::prelude::*;
use pyo3::types::IntoPyDict;
use pyo3::types::PyDict;
type MyClosure<'a> = dyn Fn() -> f64 + Send + Sync + 'a;
fn main() -> Result<(), ()>
{
match Python::with_gil(|py| main_(py)
.map_err(|e| e.print_and_set_sys_last_vars(py)))
{
Ok(handles) => {
for handle in handles {
handle.join().unwrap();
}},
Err(e) => { println!("{:?}", e); },
}
Ok(())
}
fn main_(py: Python) -> PyResult<Vec<JoinHandle<()>>>
{
let sys = py.import("sys")?;
let version = sys.get("version")?.extract::<String>()?;
let locals = [("os", py.import("os")?)].into_py_dict(py);
let code = "os.getenv('USER') or os.getenv('USERNAME') or 'Unknown'";
let user = py.eval(code, None, Some(&locals))?.extract::<String>()?;
println!("Hello {}, I'm Python {}", user, version);
// The thread will do something with the `locals` dictionary. In order to
// pass this reference object to the thread, first convert it to a
// non-reference object.
// Convert `locals` to `PyObject`.
let locals_obj = locals.to_object(py);
// Now we can move `locals_obj` into the thread without concern.
let closure: Arc<MyClosure<'_>> = Arc::new(move || {
// We can print out the PyObject which reveals it to be a tuple
// containing a pointer value.
println!("{:?}", locals_obj);
// If we want to do anything with the `locals` object, we can cast it
// back to a `PyDict` reference. We'll need to acquire the GIL first.
Python::with_gil(|py| {
// We have the GIL, cast the dict back to a PyDict reference.
let py_dict = locals_obj.cast_as::<PyDict>(py).unwrap();
// Printing it out reveals it to be a dictionary with the key `os`.
println!("{:?}", py_dict);
});
1.
});
let data = vec![closure];
let more = "Important data.".to_string();
let handles = start(data, more);
Ok(handles)
}
fn start<'a>(data : Vec<Arc<MyClosure<'static>>>,
more : String
) -> Vec<JoinHandle<()>>
{
let mut handles = vec![];
for _ in 1..=4 {
let cloned_data = data.clone();
let cloned_more = more.clone();
let h = thread::spawn(move || foo(cloned_data, cloned_more));
handles.push(h);
}
handles
}
fn foo<'a>(data : Vec<Arc<MyClosure<'a>>>,
more : String)
{
for closure in data {
closure();
}
}输出:
Hello todd, I'm Python 3.8.10 (default, Jun 2 2021, 10:49:15)
[GCC 9.4.0]
Py(0x7f3329ccdd40)
Py(0x7f3329ccdd40)
Py(0x7f3329ccdd40)
{'os': <module 'os' from '/usr/lib/python3.8/os.py'>}
{'os': <module 'os' from '/usr/lib/python3.8/os.py'>}
{'os': <module 'os' from '/usr/lib/python3.8/os.py'>}
Py(0x7f3329ccdd40)
{'os': <module 'os' from '/usr/lib/python3.8/os.py'>}需要考虑的是:您可能能够最小化或消除将Python对象传递给线程的需要,方法是将所需的所有信息提取到Rust对象中,然后将这些信息传递给线程。
https://stackoverflow.com/questions/68426839
复制相似问题