并行字计数与Rust中的多线程

问题描述 投票:-1回答:1

我想计算一个大字符串中的单词频率。

简单的单线程解决方案看起来像这样

use hashbrown::HashMap;

fn main() {
    let buffer = String::from("Hello World Hello Rust");
    let mut frequency: HashMap<&str, u32> = HashMap::new();

    for word in buffer.split_whitespace() {
        *frequency.entry(word).or_insert(0) += 1;
    }
}

然后我尝试添加一些多线程功能,最后得到以下代码:

extern crate crossbeam;

use hashbrown::HashMap;
use std::sync::{Arc, Mutex};

fn main() {
    let buffer = Arc::new(String::from("Hello World Hello Rust"));
    let frequency: Arc<Mutex<HashMap<&str, u32>>> = Arc::new(Mutex::new(HashMap::new()));

    crossbeam::scope(|scope| {
        for _ in 0..1 {
            let buffer = buffer.clone();
            let frequency = frequency.clone();

            scope.spawn(move |_| {
                for word in buffer.split_whitespace() {
                    let mut frequency = frequency.lock().unwrap();
                    *frequency.entry(word).or_insert(0) += 1;
                }
            });
        }
    });
}

编译器失败并显示以下消息:

error[E0597]: `buffer` does not live long enough
  --> src/main.rs:16:29
   |
13 |             let frequency = frequency.clone();
   |                 --------- lifetime `'1` appears in the type of `frequency`
...
16 |                 for word in buffer.split_whitespace() {
   |                             ^^^^^^ borrowed value does not live long enough
17 |                     let mut frequency = frequency.lock().unwrap();
18 |                     *frequency.entry(word).or_insert(0) += 1;
   |                      --------------------- argument requires that `buffer` is borrowed for `'1`
19 |                 }
20 |             });
   |             - `buffer` dropped here while still borrowed

为了简化代码,我删除了字符串分块,并且只在for循环中产生了1个线程。

multithreading rust
1个回答
1
投票

Scoped线程允许您借用线程外部的东西并在线程内部使用它。他们不能允许你做相反的事情(借用线程中的东西让它逃脱)。

buffer.split_whitespace()借用buffer,它已被移入内部封闭,因此由当前线程拥有。每个word都是一个依赖于buffer的生命周期的引用,当线程退出时它将超出范围。 (潜在的String没有被破坏,但word只能借用Arc,它的寿命更短。如果你只是克隆了一个String,情况也是如此)。

Arc和范围线程有点不一致。当你在线程之间共享一个东西并且你想要在最后一个线程退出时销毁这个东西时使用Arc。你通常不知道或关心哪个线程是破坏它的线程,只是它被破坏了。另一方面,当您确实知道应该销毁某个东西的位置时,会使用作用域线程,并且所有想要访问它的线程必须在此之前完全退出。因为生命周期是使用范围线程进行静态验证的,所以您可以使用普通的&引用而不是Arc。这适用于StringMutex

所以我们应用这个:

let buffer = String::from("Hello World Hello Rust");
let frequency: Mutex<HashMap<&str, u32>> = Mutex::new(HashMap::new());

crossbeam::scope(|scope| {
    for _ in 0..1 {
        scope.spawn(|_| {
            for word in buffer.split_whitespace() {
                let mut frequency = frequency.lock().unwrap();
                *frequency.entry(word).or_insert(0) += 1;
            }
        });
    }
});

哦,这很容易。请注意,没有moves,没有Arcs,也没有clone()s,而frequency将包含对buffer的引用,这可能是你想要的。为了使这个工作,你使用的任何字符串分块方法也必须从原来的str借用;你不能为每个线程单独的String

警告

我不确定你的例子与原始代码的确有多相似。上面的解决方案修复了编译问题,但正如Shepmaster所指出的那样:

我补充说原始算法不是很有效,因为HashMap的争用量将是极端的。每个线程拥有自己的HashMap并在最后合并它们可能会更有效率。 [...] Something like this

也可以看看

© www.soinside.com 2019 - 2024. All rights reserved.