TokioMiddleExperience
Какую инженерную проблему Tokio (Rust) решает в build/runtime/systems-разработке, если не сводить ответ к определению?
Tokio решает проблему масштабируемого I/O без потоков ОС: один поток work-stealing планировщика ведёт тысячи задач, устраняя накладные расходы на переключение контекста и синхронизацию mutex-ов на уровне ОС.
Инженерная проблема, которую решает Tokio
Классический подход «один поток на соединение» упирается в лимиты ОС: каждый поток стоит ~8 МБ RSS и несколько микросекунд на context switch. При 10 000 одновременных соединений это уже ~80 ГБ и постоянные переключения. Tokio устраняет эту проблему через кооперативное мультиплексирование задач поверх небольшого пула потоков.
Что конкретно происходит внутри
- Work-stealing scheduler: пул из N потоков (по умолчанию — число CPU). Каждый поток имеет локальную деку задач. Если она пуста, поток «крадёт» задачи у соседей, минимизируя простой.
- Async I/O через epoll/kqueue/IOCP: системные вызовы переведены в неблокирующий режим. Когда задача ожидает данные из сокета, она возвращает управление планировщику (
Poll::Pending), а не блокирует поток. - Waker API: когда ядро сигнализирует о готовности дескриптора, runtime пробуждает конкретную задачу через её
Wakerи помещает её обратно в деку. - Task = stackless coroutine: состояние хранится в heap-аллоцированном state machine, а не в стеке потока. Это позволяет держать миллионы задач с минимальным потреблением памяти.
Пример: эхо-сервер на 100k соединений
use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let listener = TcpListener::bind("0.0.0.0:8080").await?;
loop {
let (mut socket, addr) = listener.accept().await?;
// каждое соединение — отдельная task, не поток
tokio::spawn(async move {
let mut buf = vec![0u8; 4096];
loop {
match socket.read(&mut buf).await {
Ok(0) => break,
Ok(n) => {
if socket.write_all(&buf[..n]).await.is_err() {
break;
}
}
Err(_) => break,
}
}
println!("Connection closed: {addr}");
});
}
}
Здесь 100 000 одновременных соединений займут ~100 000 задач, но физических потоков — лишь 8 (по числу CPU). Стандартный thread-per-connection подход при тех же условиях требовал бы 800 ГБ RSS.
Почему это важно для build/systems-разработки
- Компилятор Rust проверяет Send/Sync для задач — race conditions на этапе сборки, а не в продакшне.
- Tokio интегрируется с
tracingиtokio-consoleдля live-диагностики задач без перезапуска. - Нулевая стоимость абстракций: если async-функция не используется конкурентно, компилятор может оптимизировать её до простого синхронного кода.
Подводные камни
- Blocking код в async-контексте: вызов
std::thread::sleepили синхронного I/O блокирует весь worker-поток. Используйтеtokio::time::sleepиtokio::task::spawn_blocking. - CPU-bound задачи голодают планировщик: длинный цикл без
.awaitне отдаёт управление. Добавляйтеtokio::task::yield_now().awaitили переносите вspawn_blocking. - Нельзя смешивать рантаймы: вызов async-кода одного Tokio runtime из другого вызывает панику «cannot start a runtime from within a runtime».
- Забытый
JoinHandle: если не.awaitхендл и не сохранить его, задача будет работать до завершения runtime, утекая ресурсы. - Неправильный размер пула:
#[tokio::main]по умолчанию создаёт multi-thread runtime. Для CLI-утилит без конкурентности лучше#[tokio::main(flavor = "current_thread")]. - Panic в spawn не propagate-ится: паника внутри
tokio::spawnне падает родительскую задачу, а возвращается черезJoinHandle::awaitкакErr(JoinError). - Starvation при несбалансированном spawn: если одна задача создаёт тысячи дочерних без контроля, очередь растёт бесконтрольно. Используйте
tokio::sync::Semaphoreдля rate-limiting.
What hurts your answer
- Знать термины Tokio (Rust), но не понимать связи между абстракциями
- Объяснять поведение через отдельные примеры вместо причинной модели
- Не связывать mental model с диагностикой ошибок
What they're listening for
- Понимает ключевые абстракции Tokio (Rust)
- Может предсказывать поведение системы через mental model
- Связывает модель с debugging и production decisions