Параллельный подсчет слов с многопоточностью в Rust - PullRequest
0 голосов
/ 28 марта 2019

Я хочу посчитать частоту слов в большой строке.

Простое однопоточное решение выглядит следующим образом

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

Чтобы упростить код, я удалил фрагмент строки и создал только 1 поток внутри цикла for.

1 Ответ

1 голос
/ 28 марта 2019

Потоки с областью видимости позволяют вам заимствовать что-то вне потока и использовать его внутри потока.Они не могут позволить вам сделать обратное (заимствовать что-то внутри нити и позволить ему убежать).

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 для каждого потока.

Предостережение

Я не совсем уверен, насколько ваш пример похож на исходный код.Приведенное выше решение устраняет проблему компиляции, но, как указывает Шепмастер:

Я бы добавил, что оригинальный алгоритм не очень эффективен, так как количество разногласий для HashMap будет крайне,Скорее всего, для каждого потока будет гораздо эффективнее иметь HashMap и объединять их в конце.[...] Примерно так

См. Также

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...