多个线程如何共享迭代器?

时间:2017-07-25 19:32:33

标签: multithreading rust borrowing

我一直致力于使用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

有人可以解释我应该如何修复生命周期,以便可以从线程访问迭代器?

1 个答案:

答案 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);
        }
    });
}

存在多个相关问题:

  1. 迭代器存在于堆栈中,线程的闭包引用它。
  2. 闭包对iterator采用 mutable 引用。
  3. 迭代器本身具有对存在于堆栈中的Vec的引用。
  4. Vec本身引用了可能在堆栈上生效的字符串切片,但无法保证比线程更长寿。
  5. 换句话说,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);
                });
            });
        }
    }
    

    如上所述,这只会一次产生一个线程,等待它完成。获得更多并行性的替代方法(本练习的通常要点)是将调用交换为nextspawn。这需要通过s关键字将dmove的所有权转移到该主题:

    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是一个允许轻松并行化某些类型迭代器的包。

    另见: