Вступление#
Иногда программа “замирает” в ожидании задач. Язык Rust, чтобы справиться с ними и избежать простоя, для нас приготовил три разные типа исполнения: синхронность, асинхронность и параллельность. Чтобы разобраться с ними, мы снова отправимся на борт «Vectoria», где находчивый капитан Нова и робот RUST-Y учатся работать с Tokio - рантаймом, который умеет переключать тысячи лёгких задач без простоя.
join!
, join_all
, spawn
, JoinSet
и другим которые вы встретите в этой статье. Поэтому совет - читайте внимательно, это пригодится при прохождении квиза.Готовы? Открываем новую главу.
Пролог. Врата#
Корабль “Vectoria” медленно дрейфовал у древних космических врат. Белёсые дуги уходили в пустоту, будто застывшие волны. На мостике царила напряжённая тишина.
RUST-Y, бортовой робот, переминался на месте: его сенсоры мигали тревожно.
Наконец он собрался с духом и заговорил, голос дрогнул:
— “Капитан”, разрешите обратиться и задать вопрос… Я не понимаю разницы между синхронным и асинхронным кодом. И ещё - все упоминают Tokio. Что это? И чем же асинхронность отличается от параллельности?
Экипаж оживился. Такие вопросы всегда означали, что сейчас будет короткое введени в новую тему - и обычно за этим следовало приключение.
Капитан Нова активировал голографический проектор. В воздухе появились три сцены:
- Синхронный вызов: команда корабля отправляет запрос на сервер и застывает в ожидании ответа. Экипаж ждёт, пока сервер думает и ответит.
- Асинхронный вызов: запрос ушёл — но команда продолжает жить своей жизнью: прокладывает курс, регулирует питание, сканирует окрестности. Когда сервер ответит, задача “сообщит о готовности”.
- Параллельность: несколько членов экипажа работают одновременно - Зори у навигации, Хекс у связи, Арчи в лазарете. Это потоки ОС: тяжёлые, у каждого свой стек и ресурсы.

Future
) поверх ограниченного числа потоков ОС. Каждая задача знает, когда уступить выполнение (.await
). Именно так программа остаётся всегда живой и готовой к выполнению работы.Часть 1. Когда корабль уснул#
Вдруг неожиданно раздался синал тревоги и лампа вспыхнула на мостике: красный свет, гул сирен. Сенсоры дальнего сканирования перестали отвечать. Зори в отчаянии ударил ладонью по панели:
— “Капитан! Навигация замерла. Даже связь упала!”
RUST-Y в панике вывел код на экран:
Нова нахмурился:
— “Ты заставил весь корабль спать вместе с датчиком. thread::sleep
блокирует поток. Это словно дежурный задремал на вахте. Пока он спит — системы корабля парализованы”.
Доктор Арчи добавил:
— “Это худшая сторона синхронного кода. В боевых условиях такие паузы смертельно опасны”.
Часть 2. Потоки и их цена#
Инженер Спаркс пододвинулся к консоли:
— “Предлагаю вынести тяжёлую работу в отдельный поток. Пусть остальное живёт!”
На голограмме вспыхнул новый фрагмент:
Системы ожили, связь вернулась. Экипаж облегчённо выдохнул.
Но капитан Нова покачал головой:
— “Каждый поток операционной системы забирает мегабайты памяти. Это временное средство. Мы не можем плодить их тысячами”.
Часть 3. Искусство уступать#
Через час Vectoria готовилась к гиперпрыжку. Сенсоры снова были перегружены.
Зори докладывал:
— “Если будем ждать по пять секунд синхронно, нас размажет о врата!”
Капитан спокойно включил новую голограмму:
— “Вот это правильный путь. Мы ждём, но не блокируем. Сенсор думает, а корабль продолжает подготовку. Это и есть сила асинхронности”.
Часть 4. Задачи и обещания#
RUST-Y вновь задал вопрос:
— “А если функция не async? Как вернуть результат?”
Доктор Арчи поправил очки:
— “Тогда мы возвращаем JoinHandle
. Это обещание, что задача выполнится”.
Нова добавил:
— “Но помни: если не дождаться .await
, результат уйдёт в пустоту”.
Экипаж понял: JoinHandle
- это не просто объект, а инструмент управления задачей.
Часть 5. Мост между мирами#
Корабль принял сигнал бедствия. Нужно было пропустить сигнал через модуль диагностики прямо из потока операционной системы.
Спаркс нахмурился:
— “Наш код асинхронный (async). Как его связать с обычным потоком?”
Капитан показал:
— “Handle
позволяет ОС-потоку вызвать асинхонную-задачу. Но помните: поток тяжёлый, используем только в исключительных случаях”.
Часть 6. Блокировка в сердце шторма#
Начинался космический шторм, медлить больше было нельзя.
Во время шторма у врат RUST-Y снова вставил thread::sleep
в async-код.
Навигация застыла, индикаторы мигнули красным.
— “Опять корабль уснул!” — закричал Зори.
Нова строго посмотрел на код:
Он тут же внес исправление (шаг 2). 👆🏻
Свет тревоги погас, корабль вернулся в строй.
Часть 7. Научиться делиться временем выполнения#
Команда выполнила тренировочный пробный манёвр и навигация сожрала все ресурсы процессора. Связь и сенсоры замерли.
Нова объяснил:
— “В async Rust Tokio многозадачность кооперативная. Задачи должны уступать сами.
Без .await
или yield_now().await
— планировщик бессилен”.
Часть 8. Экипаж действует как единое целое#
Перед прыжком нужно было:
- проверить реактор,
- перезапустить связь,
- откалибровать навигацию.
Капитан показал решение:
— “А если задач десятки, сотни?” — спросил Расти.
tokio::join!
— фиксированный набор задач → кортеж.futures::future::join_all
— коллекция задач →Vec
результатов.
Часть 9. Ремонтные дроны и фоновая работа#
— “Запустить ремонтных дронов!” — приказал капитан.
RUST-Y замялся:
— “Как запустить задачу в фоне?”
Нова вывел на экран код:
Хекс уточнил:
— “А если дронов много?”
Нова вывел другой код на на экран:
Эпилог. Ожидающее будущее#
Шторм утих. Сенсоры работали, все подсистемы были в порядке, дроны вернулись.
RUST-Y стоял на мостике:
— “Спасибо, капитан. Теперь я понял: ошибки — это ступени развития. Async и Future — это жизнь без блокировок”.
Нова улыбнулся:
— “Ничего, мы понемногу учимся жить в ритме вселенной. А теперь готовы к новым испытаниям”.
Врата активировались и Vectoria умчалась в даль скозь сияние портала.
Приложение: шпаргалка капитана по ключевым приёмам#
- Не блокируйте:
thread::sleep
→ заменяйте наtokio::time::sleep
. - Тяжёлое CPU/блокирующее I/O: оборачивайте в
tokio::task::spawn_blocking
. - Старт из sync-кода: используйте
tokio::runtime::Handle::block_on
. - Параллельное ожидание:
tokio::join!
для фиксированных задач;futures::join_all
для коллекций. - Фоновая работа:
tokio::spawn
(требует'static
илиmove
). - Пачки задач:
tokio::task::JoinSet
+join_next().await
. - Кооперативность:
.await
; при долгих циклах —yield_now().await
.
Финальный квиз 🚀#
🛰️