我一直致力于使用Rust和线程将一堆文件从源复制到目标的函数。我在线程共享迭代器时遇到了一些麻烦。我还不习惯借用系统:
extern crate libc;
extern crate num_cpus;
use libc::{c_char, size_t};
use std::thread;
use std::fs::copy;
fn python_str_array_2_str_vec<T, U, V>(_: T, _: U) -> V {
unimplemented!()
}
#[no_mangle]
pub extern "C" fn copyFiles(
sources: *const *const c_char,
destinies: *const *const c_char,
array_len: size_t,
) {
let src: Vec<&str> = python_str_array_2_str_vec(sources, array_len);
let dst: Vec<&str> = python_str_array_2_str_vec(destinies, array_len);
let mut iter = src.iter().zip(dst);
let num_threads = num_cpus::get();
let threads = (0..num_threads).map(|_| {
thread::spawn(|| while let Some((s, d)) = iter.next() {
copy(s, d);
})
});
for t in threads {
t.join();
}
}
fn main() {}
我收到了这个我无法解决的编译错误:
error[E0597]: `src` does not live long enough
--> src/main.rs:20:20
|
20 | let mut iter = src.iter().zip(dst);
| ^^^ does not live long enough
...
30 | }
| - borrowed value only lives until here
|
= note: borrowed value must be valid for the static lifetime...
error[E0373]: closure may outlive the current function, but it borrows `**iter`, which is owned by the current function
--> src/main.rs:23:23
|
23 | thread::spawn(|| while let Some((s, d)) = iter.next() {
| ^^ ---- `**iter` is borrowed here
| |
| may outlive borrowed value `**iter`
|
help: to force the closure to take ownership of `**iter` (and any other referenced variables), use the `move` keyword, as shown:
| thread::spawn(move || while let Some((s, d)) = iter.next() {
我已经看到了以下问题:
Value does not live long enough when using multiple threads
我没有使用chunks
,我想尝试通过线程共享一个迭代器,尽管创建块将它们传递给线程将是经典的解决方案。
Unable to send a &str between threads because it does not live long enough 我已经看到了使用频道与线程进行通信的一些答案,但我对使用它们并不十分肯定。应该有一种更简单的方法来通过线程共享一个对象。
Why doesn't a local variable live long enough for thread::scoped
这引起了我的注意,scoped
应该可以解决我的错误,但由于它处于不稳定的通道中,我想看看是否有另一种方法只使用spawn
。
有人可以解释我应该如何修复生命周期,以便可以从线程访问迭代器?
答案 0 :(得分:5)
这是你问题的MCVE:
use std::thread;
fn main() {
let src = vec!["one"];
let dst = vec!["two"];
let mut iter = src.iter().zip(dst);
thread::spawn(|| {
while let Some((s, d)) = iter.next() {
println!("{} -> {}", s, d);
}
});
}
存在多个相关问题:
Vec
的引用。Vec
本身引用了可能在堆栈上生效的字符串切片,但无法保证比线程更长寿。换句话说,Rust编译器已经阻止你执行四个独立的内存不安全。
要识别的主要事情是,您生成的任何线程可能比您生成它的地方更长。即使您立即调用join
,编译器也无法静态验证是否会发生这种情况,因此必须采取保守路径。这是scoped threads的要点 - 它们保证线程在它们启动的堆栈帧之前退出。
此外,您尝试在多个并发线程中使用可变引用。 零保证可以并行安全地调用迭代器(或它构建的任何迭代器)。完全有可能两个线程同时在 时调用next
。这两段代码并行运行并写入相同的内存地址。一个线程写入一半数据而另一个线程写入另一半,现在您的程序将在某个任意点崩溃。
使用像crossbeam这样的工具,您的代码看起来像:
extern crate crossbeam;
fn main() {
let src = vec!["one"];
let dst = vec!["two"];
let mut iter = src.iter().zip(dst);
while let Some((s, d)) = iter.next() {
crossbeam::scope(|scope| {
scope.spawn(|| {
println!("{} -> {}", s, d);
});
});
}
}
如上所述,这只会一次产生一个线程,等待它完成。获得更多并行性的替代方法(本练习的通常要点)是将调用交换为next
和spawn
。这需要通过s
关键字将d
和move
的所有权转移到该主题:
extern crate crossbeam;
fn main() {
let src = vec!["one", "alpha"];
let dst = vec!["two", "beta"];
let mut iter = src.iter().zip(dst);
crossbeam::scope(|scope| {
while let Some((s, d)) = iter.next() {
scope.spawn(move || {
println!("{} -> {}", s, d);
});
}
});
}
如果在spawn
内添加一个睡眠调用,您可以看到并行运行的线程。
我已经使用for
循环编写了它,但是:
let iter = src.iter().zip(dst);
crossbeam::scope(|scope| {
for (s, d) in iter {
scope.spawn(move || {
println!("{} -> {}", s, d);
});
}
});
最后,迭代器在当前线程上运行,然后从迭代器返回的每个值都传递给新线程。保证新线程在捕获的引用之前退出。
您可能对Rayon感兴趣,Lifetime troubles sharing references between threads是一个允许轻松并行化某些类型迭代器的包。
另见: