TokioMiddleTechnical

В чём разница между однопоточным и многопоточным runtime в Tokio?

current_thread запускает всё в одном OS-потоке (можно Rc/RefCell, нет Send-ограничений); multi_thread создаёт пул worker-потоков с work-stealing для параллельных I/O и CPU-задач, но требует Send + 'static.

Два режима Tokio Runtime

Tokio предоставляет два варианта runtime: однопоточный (current_thread) и многопоточный (multi_thread). Выбор влияет на то, сколько OS-потоков используется, как задачи планируются и какие типы данных можно безопасно использовать в async-коде.

Однопоточный runtime (current_thread)

Весь async-код выполняется в одном OS-потоке. Планировщик — кооперативный: задачи уступают управление только при .await. Это означает, что данные не обязаны быть Send — можно использовать Rc<T>, RefCell<T> и другие не-Send типы прямо в futures.

use tokio::runtime::Builder;

fn main() {
    let rt = Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();

    rt.block_on(async {
        // Rc можно использовать, т.к. один поток
        let data = std::rc::Rc::new(42);
        println!("value: {}", data);
    });
}

Макрос #[tokio::test] по умолчанию использует current_thread, что упрощает написание тестов без многопоточных гонок.

Многопоточный runtime (multi_thread)

По умолчанию запускает N worker-потоков (обычно равно числу логических CPU). Задачи могут мигрировать между потоками через work-stealing планировщик. Все типы, помещаемые в задачи, должны быть Send + 'static.

use tokio::runtime::Builder;

fn main() {
    let rt = Builder::new_multi_thread()
        .worker_threads(4)          // явно задаём число потоков
        .thread_name("my-worker")
        .enable_all()
        .build()
        .unwrap();

    rt.block_on(async {
        let handle = tokio::spawn(async {
            // этот код может выполниться на любом из 4 потоков
            heavy_computation().await
        });
        handle.await.unwrap();
    });
}

async fn heavy_computation() -> u64 {
    tokio::task::spawn_blocking(|| {
        // CPU-bound работа вне async-контекста
        (0..1_000_000u64).sum()
    })
    .await
    .unwrap()
}

Атрибут #[tokio::main]

По умолчанию #[tokio::main] разворачивается в multi_thread. Для явного указания:

// Многопоточный (по умолчанию)
#[tokio::main]
async fn main() { /* ... */ }

// Однопоточный
#[tokio::main(flavor = "current_thread")]
async fn main() { /* ... */ }

// Многопоточный с явным числом потоков
#[tokio::main(worker_threads = 2)]
async fn main() { /* ... */ }

Когда что использовать

  • current_thread — CLI-инструменты, тесты, embedded-системы, задачи с не-Send данными (например, Rc, FFI-указатели)
  • multi_thread — HTTP-серверы (Axum, Actix), gRPC-сервисы, любые workload с параллельными I/O-запросами или CPU-bound задачами через spawn_blocking

Подводные камни

  • Долгая синхронная работа (std::thread::sleep, тяжёлые вычисления) в current_thread блокирует весь runtime — другие задачи не получат CPU до завершения
  • В multi_thread нельзя хранить Rc<T> или Cell<T> через точку .await — компилятор выдаст ошибку «does not implement Send»
  • spawn_blocking создаёт отдельный пул потоков (по умолчанию до 512); при злоупотреблении можно исчерпать OS-потоки
  • Work-stealing в multi_thread может вызвать cache thrashing при очень маленьких задачах — лучше батчить мелкую работу
  • При создании нескольких runtime через Builder каждый держит свои OS-потоки — легко непреднамеренно создать избыток потоков
  • tokio::runtime::Handle::current() паникует вне контекста runtime — нельзя вызывать из обычного синхронного кода без block_on
  • Вложенный block_on (вызов rt.block_on внутри уже выполняющегося async-кода) вызывает панику — используйте tokio::spawn вместо этого

Common mistakes

  • Отвечать определением без production-сценария.
  • Не называть runtime boundary, security boundary или failure mode.
  • Игнорировать версию API, observability и тестовую проверку.

What the interviewer is testing

  • Объясняет механизм своими словами и без выдуманных API.
  • Называет реальные риски, диагностику и критерий корректности.
  • Связывает ответ с текущей документацией и миграционными ограничениями.

Sources

Related topics