首页 > 解决方案 > 如何以线程安全的方式使用 Rayon 的 par_iter 读取和修改变量?

问题描述

这段代码:

use rayon::prelude::*; // 1.5.0

fn main() {
    let mut items = Vec::new();
    items.push("hello");
    items.push("foo");
    items.push("bar");
    items.push("ipsum");

    let mut counter = 0;

    let results = items.par_iter().map(|item| {
        // do something time consuming with item
        counter += 1;
        print!("completed {} items\r", counter);
        0
    });
}

产生错误:

warning: unused variable: `item`
  --> src/main.rs:12:41
   |
12 |     let results = items.par_iter().map(|item| {
   |                                         ^^^^ help: if this is intentional, prefix it with an underscore: `_item`
   |
   = note: `#[warn(unused_variables)]` on by default

warning: unused variable: `results`
  --> src/main.rs:12:9
   |
12 |     let results = items.par_iter().map(|item| {
   |         ^^^^^^^ help: if this is intentional, prefix it with an underscore: `_results`

error[E0594]: cannot assign to `counter`, as it is a captured variable in a `Fn` closure
  --> src/main.rs:14:9
   |
14 |         counter += 1;
   |         ^^^^^^^^^^^^ cannot assign

标签: multithreadingrustrayon

解决方案


Rust 通过从两个不同的线程写入同一个变量来防止你在这里发生数据竞争。你有几个选择如何解决这个问题。这真的取决于具体情况。

  1. 最简单的是使用Mutexfor counter。这使您可以安全地访问相同的变量。引入Mutex有耗尽并行迭代器所有加速的风险,因为一切都将通过Mutex访问获得顺序。map如果运行时间很大并且锁定了Mutex短路,这是可以接受的。
  2. 对于计数器原子类型的特定情况,例如AtomicI32运行良好,但它们很难或不可能用于更复杂的类型。
  3. 与直接聚合单个变量不同,工作可以并行完成多次,然后合并在一起。这就是reducerayon 的 -functions 所做的。每个线程将至少有一个计数器,它们将被合并在一起以产生一个最终结果。

推荐阅读