我想计算一个大字符串中的单词频率。
简单的单线程解决方案看起来像这样
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个线程。
Scoped线程允许您借用线程外部的东西并在线程内部使用它。他们不能允许你做相反的事情(借用线程中的东西让它逃脱)。
buffer.split_whitespace()
借用buffer
,它已被移入内部封闭,因此由当前线程拥有。每个word
都是一个依赖于buffer
的生命周期的引用,当线程退出时它将超出范围。 (潜在的String
没有被破坏,但word
只能借用Arc
,它的寿命更短。如果你只是克隆了一个String
,情况也是如此)。
Arc
和范围线程有点不一致。当你在线程之间共享一个东西并且你想要在最后一个线程退出时销毁这个东西时使用Arc
。你通常不知道或关心哪个线程是破坏它的线程,只是它被破坏了。另一方面,当您确实知道应该销毁某个东西的位置时,会使用作用域线程,并且所有想要访问它的线程必须在此之前完全退出。因为生命周期是使用范围线程进行静态验证的,所以您可以使用普通的&
引用而不是Arc
。这适用于String
和Mutex
。
所以我们应用这个:
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;
}
});
}
});
哦,这很容易。请注意,没有move
s,没有Arc
s,也没有clone()
s,而frequency
将包含对buffer
的引用,这可能是你想要的。为了使这个工作,你使用的任何字符串分块方法也必须从原来的str
借用;你不能为每个线程单独的String
。
我不确定你的例子与原始代码的确有多相似。上面的解决方案修复了编译问题,但正如Shepmaster所指出的那样:
我补充说原始算法不是很有效,因为
HashMap
的争用量将是极端的。每个线程拥有自己的HashMap
并在最后合并它们可能会更有效率。 [...] Something like this