Передача данных в потоки
Правильно используйте замыкания move.
«Передача данных в потоки» — бесплатный урок Learn Rust Coding на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Learn Rust Coding, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Learn Rust Coding содержит 4 уроков всего.
Замыкания захватывают окружение
Замыкание, переданное в thread::spawn, может использовать переменные из окружающей области видимости. По умолчанию Rust заимствует их.
Но запущенный поток может пережить функцию, которая его создала, поэтому обычное заимствование небезопасно. Rust отклоняет такой код во время компиляции.
Проблема заимствования
Если поток заимствует локальную переменную, компилятор не может доказать, что переменная будет существовать достаточно долго. Поток может продолжить работу после удаления переменной.
Приведённый ниже код не компилируется, потому что замыкание лишь заимствует data.
use std::thread;
fn main() {
let data = vec![1, 2, 3];
// ERROR: closure may outlive `data`
let h = thread::spawn(|| {
println!("{:?}", data);
});
h.join().unwrap();
}Ключевое слово <code>move</code>
Добавление move перед замыканием заставляет его получить владение захваченными переменными. Они перемещаются в поток.
Теперь поток владеет данными, поэтому гарантируется, что они останутся действительными на протяжении времени жизни потока.
use std::thread;
fn main() {
let data = vec![1, 2, 3];
let h = thread::spawn(move || {
println!("{:?}", data);
});
h.join().unwrap();
}Передача владения наружу
После перемещения значения в поток исходная область видимости больше не может его использовать. Владение перешло в другое место.
Попытка использовать data в main после перемещения приведёт к ошибке компиляции. Теперь поток является единственным владельцем.
use std::thread;
fn main() {
let data = vec![1, 2, 3];
let h = thread::spawn(move || println!("{:?}", data));
// println!("{:?}", data); // ERROR: value moved
h.join().unwrap();
}Перемещение типов Copy
Типы, реализующие Copy, например целые числа, копируются, а не перемещаются. Замыкание получает собственную копию.
Поэтому после захвата числа замыканием с move вы всё ещё можете использовать исходное значение в main.
use std::thread;
fn main() {
let n = 42;
let h = thread::spawn(move || {
println!("thread sees {}", n);
});
println!("main still sees {}", n);
h.join().unwrap();
}Send: безопасная передача
Тип можно переместить в другой поток, только если он реализует признак Send. Send означает, что передача владения между потоками безопасна.
Большинство типов автоматически являются Send. Некоторые, например Rc, им не являются, и компилятор отклонит попытку их переместить.
Rc не является Send
Rc — это счётчик ссылок для одного потока. Его счётчик не защищён от одновременных обновлений, поэтому Rc не является Send.
Попытка переместить Rc в поток завершится ошибкой компиляции. В следующем уроке вместо него вы будете использовать Arc.
use std::rc::Rc;
use std::thread;
fn main() {
let r = Rc::new(5);
// ERROR: `Rc<i32>` cannot be sent between threads safely
let h = thread::spawn(move || println!("{}", r));
h.join().unwrap();
}Перемещение нескольких значений
Одно замыкание с move может одновременно захватить несколько переменных. Все они перемещаются в поток.
Это удобно, когда рабочему потоку нужны и входные данные, и метка.
use std::thread;
fn main() {
let label = String::from("sum");
let nums = vec![1, 2, 3, 4];
let h = thread::spawn(move || {
let total: i32 = nums.iter().sum();
println!("{} = {}", label, total);
});
h.join().unwrap();
}Клонирование перед перемещением
Если значение нужно и потоку, и главной функции, сначала клонируйте его. Одну копию передайте потоку, а другую оставьте себе.
Клонирование копирует данные, поэтому каждая сторона владеет независимым значением.
use std::thread;
fn main() {
let original = String::from("hello");
let for_thread = original.clone();
let h = thread::spawn(move || println!("thread: {}", for_thread));
println!("main: {}", original);
h.join().unwrap();
}Возврат перемещённых данных
Поток, владеющий перемещёнными данными, может вернуть их, передав владение обратно родительскому потоку через join.
В этом подходе данные перемещаются внутрь, обрабатываются, а затем результат перемещается наружу.
use std::thread;
fn main() {
let mut v = vec![3, 1, 2];
let h = thread::spawn(move || {
v.sort();
v
});
let sorted = h.join().unwrap();
println!("{:?}", sorted);
}Зачем нужен move
Без move средство проверки заимствований предполагает, что замыкание заимствует данные. Поскольку у созданных потоков нет фиксированного времени жизни, связанного с вызывающим кодом, такие заимствования небезопасны.
Ключевое слово move превращает заимствования в передачу владения, выполняя требование 'static для spawn.
Быстрая проверка
Проверьте, насколько хорошо вы поняли перемещение данных в потоки.
Повторение
Вы узнали, что замыкания потоков должны владеть используемыми ими данными; этого можно добиться с помощью ключевого слова move.
Перемещение передаёт владение, а типы Copy копируются. Признак Send показывает, что безопасно передавать, а Rc не является Send.
Далее вы будете совместно использовать данные между потоками с помощью Arc и Mutex.
Часто задаваемые вопросы
Урок «Передача данных в потоки» бесплатный?
Да — полный текст урока «Передача данных в потоки» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Learn Rust Coding, подпишись на CoddyKit PRO. Курс Learn Rust Coding содержит 4 уроков всего.
Чему я научусь в уроке «Передача данных в потоки»?
Правильно используйте замыкания move. Ты практикуешь Learn Rust Coding с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Learn Rust Coding?
Предыдущий опыт не требуется. Learn Rust Coding на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Передача данных в потоки»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Learn Rust Coding?
Да. Каждый урок Learn Rust Coding включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Запуск потоков
- Передача данных в потоки
- Совместный доступ с Arc и Mutex
- Объединение потоков и сбор результатов