0Pricing
Learn Rust Coding · Урок

Каналы Crossbeam

Продвинутые каналы

«Каналы Crossbeam» — бесплатный урок Learn Rust Coding на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Learn Rust Coding, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Learn Rust Coding содержит 4 уроков всего.

За пределами std mpsc

Стандартный канал mpsc поддерживает одного получателя: получатель может быть только один. Ящик crossbeam-channel предоставляет каналы с несколькими производителями и несколькими потребителями (mpmc), дополнительными возможностями и зачастую более высокой производительностью.

Основные возможности:

  • Получатели Receiver также поддерживают клонирование, а не только отправители.
  • Мощный макрос select! для ожидания в нескольких каналах.
  • Каналы с ограниченной и неограниченной ёмкостью, а также специальные каналы, например каналы тактовых сигналов.

Добавление зависимости

Crossbeam — внешний ящик, поэтому добавьте его в Cargo.toml:

[dependencies]
crossbeam-channel = "0.5"

Затем импортируйте его функции. Поскольку здесь нужны Cargo и внешний ящик, приведённые фрагменты иллюстрируют API, а не запускаются самостоятельно.

// Cargo.toml
// [dependencies]
// crossbeam-channel = "0.5"

use crossbeam_channel::unbounded;

fn main() {
    let (s, r) = unbounded();
    s.send("hello").unwrap();
    println!("{}", r.recv().unwrap());
}

Каналы с неограниченной и ограниченной ёмкостью

Crossbeam предоставляет два основных конструктора:

  • unbounded() при необходимости расширяется; send никогда не блокируется.
  • bounded(cap) имеет буфер фиксированного размера; send блокируется, когда буфер заполнен, обеспечивая обратное давление.

Канал bounded(0) — это канал рандеву, в котором send и recv напрямую передают значение друг другу.

use crossbeam_channel::bounded;
use std::thread;

fn main() {
    let (s, r) = bounded(2);
    thread::spawn(move || {
        for i in 1..=3 {
            s.send(i).unwrap();
        }
    });
    while let Ok(v) = r.recv() {
        println!("got {}", v);
    }
}

Несколько потребителей

Главное преимущество заключается в возможности клонировать Receiver. Несколько рабочих потоков могут получать сообщения из одного канала, при этом каждое сообщение достаётся ровно одному из них. Это основа пула потоков с распределением работы.

use crossbeam_channel::unbounded;
use std::thread;

fn main() {
    let (s, r) = unbounded();
    let mut workers = vec![];
    for id in 0..3 {
        let rx = r.clone();
        workers.push(thread::spawn(move || {
            while let Ok(job) = rx.recv() {
                println!("worker {} got job {}", id, job);
            }
        }));
    }
    for job in 0..6 { s.send(job).unwrap(); }
    drop(s);
    for w in workers { w.join().unwrap(); }
}

Макрос select!

select! позволяет одному потоку ожидать несколько операций над каналами и выполнять действие с той, которая первой станет готовой. Это похоже на match для событий каналов и особенно удобно для объединения входных данных из нескольких источников.

use crossbeam_channel::{unbounded, select};
use std::thread;

fn main() {
    let (s1, r1) = unbounded();
    let (s2, r2) = unbounded();
    thread::spawn(move || { s1.send("from one").unwrap(); });
    thread::spawn(move || { s2.send("from two").unwrap(); });
    for _ in 0..2 {
        select! {
            recv(r1) -> msg => println!("r1: {}", msg.unwrap()),
            recv(r2) -> msg => println!("r2: {}", msg.unwrap()),
        }
    }
}

Тайм-ауты с select!

select! поддерживает ветвь default и ветвь таймера recv(after(duration)), поэтому после истечения тайм-аута можно прекратить ожидание вместо бесконечной блокировки. Это необходимо для отзывчивых систем.

use crossbeam_channel::{unbounded, select, after};
use std::time::Duration;

fn main() {
    let (_s, r) = unbounded::<i32>();
    select! {
        recv(r) -> msg => println!("received {:?}", msg),
        recv(after(Duration::from_millis(100))) -> _ => {
            println!("timed out waiting for a message");
        }
    }
}

try_send и try_recv

Как и стандартная библиотека, Crossbeam предоставляет неблокирующие варианты. try_send немедленно завершается ошибкой, если канал с ограниченной ёмкостью заполнен; try_recv немедленно завершается ошибкой, если готовых сообщений нет. Оба метода возвращают информативную ошибку, которую можно сопоставить с образцом.

use crossbeam_channel::{bounded, TrySendError};

fn main() {
    let (s, _r) = bounded(1);
    s.send(1).unwrap();
    match s.try_send(2) {
        Ok(()) => println!("sent"),
        Err(TrySendError::Full(v)) => println!("full, kept {}", v),
        Err(TrySendError::Disconnected(v)) => println!("closed, kept {}", v),
    }
}

tick и периодическая работа

Функция tick(duration) возвращает получателя, который доставляет сообщение через фиксированные интервалы. Объедините её с select!, чтобы выполнять периодические задачи одновременно с обработкой других каналов, например поддерживать сигнал доступности или цикл опроса.

use crossbeam_channel::{tick, select};
use std::time::Duration;

fn main() {
    let ticker = tick(Duration::from_millis(50));
    let mut count = 0;
    while count < 3 {
        select! {
            recv(ticker) -> _ => {
                count += 1;
                println!("tick {}", count);
            }
        }
    }
}

Конвейер этапов

Crossbeam отлично подходит для построения конвейеров: первый этап создаёт данные, второй преобразует их, третий потребляет. Каждый этап выполняется в собственном потоке, соединённом с другими каналами, а клонированные получатели позволяют масштабировать любой этап на несколько рабочих потоков.

use crossbeam_channel::unbounded;
use std::thread;

fn main() {
    let (in_s, in_r) = unbounded();
    let (out_s, out_r) = unbounded();
    thread::spawn(move || {
        for n in in_r { out_s.send(n * n).unwrap(); }
    });
    for n in 1..=4 { in_s.send(n).unwrap(); }
    drop(in_s);
    for sq in out_r { println!("square: {}", sq); }
}

Когда Crossbeam лучше std

Выбирайте crossbeam-channel, когда нужны:

  • Несколько потребителей, совместно использующих одну очередь (пулы рабочих потоков).
  • select! для работы со множеством каналов и тайм-аутами.
  • Периодические таймеры через tick, встроенные в выбор операции.

Для простой передачи данных от одного производителя к одному потребителю достаточно стандартного mpsc, которому не нужны зависимости.

Каналы закрываются при удалении

Как и в std, каналы Crossbeam закрываются, когда удалены все отправители (для стороны получателя) или все получатели (для стороны отправителя). Итерация по получателю завершается после удаления последнего отправителя. Всегда вызывайте drop для отправителей или ограничивайте их область действия, чтобы рабочие циклы могли корректно завершаться.

use crossbeam_channel::unbounded;
use std::thread;

fn main() {
    let (s, r) = unbounded();
    let h = thread::spawn(move || {
        let total: i32 = r.iter().sum();
        println!("total: {}", total);
    });
    for i in 1..=5 { s.send(i).unwrap(); }
    drop(s); // closes channel so the loop ends
    h.join().unwrap();
}

Быстрая проверка

Проверьте, насколько хорошо Вы понимаете каналы Crossbeam.

Итоги

Вы изучили каналы Crossbeam:

  • Они поддерживают mpmc: можно клонировать и отправителей, и получателей.
  • unbounded() и bounded(n) управляют буферизацией и обратным давлением.
  • select! ожидает данные в нескольких каналах, а тайм-ауты задаются через after, периодические события — через tick.
  • try_send/try_recv являются неблокирующими.
  • Используйте Crossbeam для пулов рабочих потоков и сложных конвейеров, а std mpsc — для простых каналов передачи данных.

Часто задаваемые вопросы

Урок «Каналы Crossbeam» бесплатный?

Да — полный текст урока «Каналы Crossbeam» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Learn Rust Coding, подпишись на CoddyKit PRO. Курс Learn Rust Coding содержит 4 уроков всего.

Чему я научусь в уроке «Каналы Crossbeam»?

Продвинутые каналы Ты практикуешь Learn Rust Coding с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Learn Rust Coding?

Предыдущий опыт не требуется. Learn Rust Coding на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «Каналы Crossbeam»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Learn Rust Coding?

Да. Каждый урок Learn Rust Coding включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Каналы mpsc
  2. Общее состояние с Arc/Mutex
  3. Потоки с областью действия
  4. Каналы Crossbeam
← Назад к Learn Rust Coding