Каналы mpsc
Обмен данными между потоками
«Каналы mpsc» — бесплатный урок Learn Rust Coding на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Learn Rust Coding, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Learn Rust Coding содержит 4 уроков всего.
Что такое канал?
Канал — это однонаправленный канал для отправки значений из одного потока в другой. Стандартная библиотека Rust предоставляет std::sync::mpsc, где mpsc означает несколько отправителей, один получатель.
- Половина отправителя помещает значения в канал.
- Половина получателя извлекает значения.
Каналы позволяют потокам обмениваться данными с помощью передачи сообщений, а не прямого совместного доступа к памяти, что предотвращает многие гонки данных.
Создание канала
Вызовите mpsc::channel(), чтобы получить кортеж (Sender, Receiver). Здесь мы отправляем одно значение из порождённого потока обратно в главный поток.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send(42).unwrap();
});
let received = rx.recv().unwrap();
println!("Got: {}", received);
}send и recv
tx.send(value) возвращает Result: операция завершается ошибкой только в том случае, если получатель был удалён. rx.recv() блокируется до поступления значения и возвращает Err, когда все отправители исчезли.
sendперемещает владение значением в канал.recvизвлекает владение значением на другой стороне.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let msg = String::from("hello from thread");
tx.send(msg).unwrap();
});
let text = rx.recv().unwrap();
println!("{}", text);
}Владение перемещается через канал
Поскольку send принимает значение по значению, использовать его после отправки нельзя. Это правило, проверяемое на этапе компиляции, гарантирует, что ни один поток не сохранит устаревшую ссылку на данные, которыми теперь владеет другой поток.
Ниже попытка вывести msg после send вызвала бы ошибку компиляции, поэтому мы используем его только один раз.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let data = vec![1, 2, 3];
tx.send(data).unwrap();
// data is moved; cannot use it here
});
let v = rx.recv().unwrap();
println!("Sum: {}", v.iter().sum::<i32>());
}Итерация по получателю
Receiver реализует IntoIterator. Цикл по нему выдаёт каждое значение, пока канал не закроется (то есть пока не будут удалены все отправители). Это идиоматичный способ обработки потока сообщений.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
for i in 1..=3 {
tx.send(i).unwrap();
}
});
for received in rx {
println!("Received: {}", received);
}
}Несколько отправителей с clone
mp в mpsc означает, что отправителей может быть несколько. Скопируйте Sender и передайте копию каждому потоку. Получатель собирает все значения, пока не будут удалены все копии.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
let tx2 = tx.clone();
thread::spawn(move || { tx.send("from A").unwrap(); });
thread::spawn(move || { tx2.send("from B").unwrap(); });
for msg in rx {
println!("{}", msg);
}
}Правила закрытия канала
Цикл получателя автоматически завершается, когда удалены все отправители. Если хотя бы один Sender остаётся активным, for msg in rx блокируется навсегда в ожидании новых значений. Всегда правильно удаляйте отправителей или ограничивайте их область действия, чтобы цикл мог завершиться.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
let handle = thread::spawn(move || {
for i in 0..3 {
tx.send(i * 10).unwrap();
}
// tx dropped here, closing the channel
});
handle.join().unwrap();
let total: i32 = rx.iter().sum();
println!("Total: {}", total);
}try_recv для неблокирующего чтения
recv блокируется, а try_recv немедленно возвращает Result. Он выдаёт Ok(value), если сообщение готово, или Err, если канал пуст или отключён. Это полезно в циклах обработки событий, которым нужно продолжать выполнять другую работу.
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
thread::sleep(Duration::from_millis(50));
tx.send("ready").unwrap();
});
loop {
match rx.try_recv() {
Ok(msg) => { println!("{}", msg); break; }
Err(_) => println!("waiting..."),
}
thread::sleep(Duration::from_millis(20));
}
}Отправка пользовательских типов
Через канал может передаваться любой тип, реализующий Send, в том числе собственные структуры и перечисления. Перечисления отлично подходят для представления разных видов сообщений в протоколе взаимодействия с рабочим потоком.
use std::sync::mpsc;
use std::thread;
enum Job {
Print(String),
Add(i32, i32),
}
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send(Job::Print(String::from("hi"))).unwrap();
tx.send(Job::Add(2, 3)).unwrap();
});
for job in rx {
match job {
Job::Print(s) => println!("print: {}", s),
Job::Add(a, b) => println!("add: {}", a + b),
}
}
}sync_channel и обратное давление
mpsc::sync_channel(n) создаёт ограниченный канал с буфером размера n. Когда буфер заполнен, send блокируется, пока не освободится место. Это создаёт обратное давление и не позволяет быстрому производителю перегрузить медленного потребителя.
sync_channel(0)— канал встречи: операции send и recv должны выполняться одновременно.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::sync_channel(2);
thread::spawn(move || {
for i in 1..=4 {
tx.send(i).unwrap();
println!("sent {}", i);
}
});
for v in rx {
println!("got {}", v);
}
}Простой шаблон рабочего потока
Каналы особенно хорошо подходят для шаблона производитель/потребитель: один поток создаёт элементы работы, а другой получает и обрабатывает их. Здесь главный поток создаёт числа, а рабочий поток возводит каждое из них в квадрат и отправляет результат обратно через второй канал.
use std::sync::mpsc;
use std::thread;
fn main() {
let (job_tx, job_rx) = mpsc::channel();
let (res_tx, res_rx) = mpsc::channel();
thread::spawn(move || {
for n in job_rx {
res_tx.send(n * n).unwrap();
}
});
for n in 1..=4 {
job_tx.send(n).unwrap();
}
drop(job_tx);
for r in res_rx {
println!("square: {}", r);
}
}Быстрая проверка
Проверьте, насколько хорошо Вы поняли работу каналов mpsc.
Итоги
Вы изучили основы каналов mpsc:
mpsc::channel()возвращает пару(Sender, Receiver).sendперемещает значение в канал, аrecvблокируется, чтобы извлечь его.- Итерация по получателю обрабатывает сообщения, пока не будут удалены все отправители.
- Скопируйте
Sender, чтобы создать несколько производителей. try_recvне блокируется, аsync_channel(n)добавляет ограниченное обратное давление.
Каналы позволяют потокам безопасно обмениваться данными, передавая владение вместо совместного доступа к памяти.
Изучай Rust с ИИ-репетитором — бесплатно
Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.
- Курсы
- 39
- Уроки
- 144
Часто задаваемые вопросы
Урок «Каналы mpsc» бесплатный?
Да — полный текст урока «Каналы mpsc» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Learn Rust Coding, подпишись на CoddyKit PRO. Курс Learn Rust Coding содержит 4 уроков всего.
Чему я научусь в уроке «Каналы mpsc»?
Обмен данными между потоками Ты практикуешь Learn Rust Coding с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Learn Rust Coding?
Предыдущий опыт не требуется. Learn Rust Coding на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.
Сколько времени занимает урок «Каналы mpsc»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Learn Rust Coding?
Да. Каждый урок Learn Rust Coding включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.