Объединение потоков и сбор результатов
Дожидайтесь потоков и собирайте результаты.
«Объединение потоков и сбор результатов» — бесплатный урок Learn Rust Coding на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Learn Rust Coding, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Learn Rust Coding содержит 4 уроков всего.
Зачем присоединять потоки
Создание потока запускает работу, но обычно вам нужен её результат или хотя бы подтверждение завершения. Присоединение — это способ дождаться этого.
В этом уроке вы соберёте дескрипторы, присоедините их и объедините созданные ими значения в итоговый ответ.
join возвращает Result
join возвращает Result. При успехе он содержит значение, возвращённое замыканием; при аварийном завершении потока — Err.
Вызов unwrap напрямую возвращает значение, но вызывает аварийное завершение, если поток завершился с ошибкой.
use std::thread;
fn main() {
let h = thread::spawn(|| 7 * 6);
let value: i32 = h.join().unwrap();
println!("answer = {}", value);
}Сначала соберите дескрипторы
Чтобы потоки выполнялись параллельно, создайте их все до присоединения любого из них. По мере создания добавляйте каждый дескриптор в вектор.
Если присоединять поток внутри цикла создания, каждый поток завершится до запуска следующего, и параллельность будет потеряна.
use std::thread;
fn main() {
let handles: Vec<_> = (0..4)
.map(|i| thread::spawn(move || i * i))
.collect();
for h in handles {
println!("{}", h.join().unwrap());
}
}Сбор результатов в Vec
Можно присоединить каждый поток и добавить возвращённые значения в вектор результатов.
Здесь и порядок создания, и порядок присоединения идут от нуля до трёх, поэтому вектор результатов получает предсказуемый порядок.
use std::thread;
fn main() {
let handles: Vec<_> = (1..=4)
.map(|n| thread::spawn(move || n * 10))
.collect();
let results: Vec<i32> = handles
.into_iter()
.map(|h| h.join().unwrap())
.collect();
println!("{:?}", results);
}Суммирование параллельных результатов
Собрав результаты, можно свести их в одно значение. Здесь каждый поток вычисляет частичное значение, а main складывает их.
Такая схема «map, затем reduce» часто используется при параллельных вычислениях.
use std::thread;
fn main() {
let handles: Vec<_> = (1..=5)
.map(|n| thread::spawn(move || n * n))
.collect();
let total: i32 = handles
.into_iter()
.map(|h| h.join().unwrap())
.sum();
println!("total = {}", total);
}Разбиение работы на части
При работе с большими объёмами данных разделите их на части и передайте каждую часть отдельному потоку. Каждый поток возвращает частичную сумму.
Клонирование каждой части в принадлежащий потоку Vec позволяет безопасно передать потоку владение своим фрагментом.
use std::thread;
fn main() {
let data: Vec<i32> = (1..=10).collect();
let handles: Vec<_> = data
.chunks(5)
.map(|c| { let c = c.to_vec(); thread::spawn(move || c.iter().sum::<i32>()) })
.collect();
let total: i32 = handles.into_iter().map(|h| h.join().unwrap()).sum();
println!("{}", total);
}Обработка аварийного завершения потока
Если рабочий поток может завершиться аварийно, не вызывайте unwrap без проверки. Сопоставьте значение Result, полученное от join, чтобы обработать ошибочный путь.
Так главный поток продолжит работу, даже если один рабочий поток завершится с ошибкой.
use std::thread;
fn main() {
let h = thread::spawn(|| {
if true { panic!("failed"); }
1
});
match h.join() {
Ok(v) => println!("got {}", v),
Err(_) => println!("thread panicked"),
}
}Сбор данных в общем Vec
Вместо возврата значений потоки могут добавлять их в общий Arc<Mutex<Vec>>. В этом случае порядок будет недетерминированным.
После присоединения всех потоков один раз заблокируйте вектор, чтобы прочитать каждый собранный результат.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let out = Arc::new(Mutex::new(vec![]));
let mut handles = vec![];
for i in 0..3 {
let o = Arc::clone(&out);
handles.push(thread::spawn(move || o.lock().unwrap().push(i)));
}
for h in handles { h.join().unwrap(); }
println!("{:?}", out.lock().unwrap());
}Потоки с областью видимости
thread::scope позволяет потокам заимствовать локальные данные без move или Arc. Область видимости гарантирует, что все потоки завершатся до её возврата.
Это делает параллельное чтение данных из стека удобным и безопасным.
use std::thread;
fn main() {
let data = vec![1, 2, 3];
thread::scope(|s| {
s.spawn(|| println!("sum {}", data.iter().sum::<i32>()));
s.spawn(|| println!("len {}", data.len()));
});
}Возврат результатов из области видимости
Потоки с областью видимости также возвращают значения через свои дескрипторы. Присоедините их внутри области видимости, чтобы собрать результаты.
Поскольку область видимости блокирует выполнение до завершения, заимствованные данные остаются действительными всё это время.
use std::thread;
fn main() {
let nums = vec![4, 5, 6];
let total = thread::scope(|s| {
let h = s.spawn(|| nums.iter().sum::<i32>());
h.join().unwrap()
});
println!("total = {}", total);
}Не забывайте присоединять потоки
Если удалить JoinHandle, не присоединив поток, поток станет отсоединённым. Он продолжит выполняться, но вы потеряете его результат и возможность дождаться его завершения.
Процесс также может завершиться до окончания работы отсоединённого потока. Выполняйте присоединение, когда вам нужны результат или подтверждение завершения.
Быстрая проверка
Проверьте, насколько хорошо вы поняли присоединение потоков и сбор результатов.
Повторение
Вы узнали, что join возвращает Result, содержащий значение потока или информацию о его аварийном завершении. Сначала создавайте все потоки, а затем присоединяйте их, чтобы сохранить параллельность.
Результаты можно собрать в Vec, свести в одно значение или добавлять в общий Arc<Mutex<Vec>>. Потоки с областью видимости безопасно заимствуют локальные данные, а удаление дескриптора отсоединяет поток.
Теперь у вас есть основные инструменты для работы с потоками и общим состоянием в Rust.
Часто задаваемые вопросы
Урок «Объединение потоков и сбор результатов» бесплатный?
Да — полный текст урока «Объединение потоков и сбор результатов» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Learn Rust Coding, подпишись на CoddyKit PRO. Курс Learn Rust Coding содержит 4 уроков всего.
Чему я научусь в уроке «Объединение потоков и сбор результатов»?
Дожидайтесь потоков и собирайте результаты. Ты практикуешь Learn Rust Coding с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Learn Rust Coding?
Предыдущий опыт не требуется. Learn Rust Coding на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Объединение потоков и сбор результатов»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Learn Rust Coding?
Да. Каждый урок Learn Rust Coding включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Запуск потоков
- Передача данных в потоки
- Совместный доступ с Arc и Mutex
- Объединение потоков и сбор результатов