Search
🦀

260524_1620_The Tokio runtime and #[tokio::main]

Run: cargo run --example 01_basic_runtime

Learning goals

Understand what a runtime does for async Rust.
Know the difference between multi-thread and current-thread flavors.
See how async fn main becomes a normal fn main that drives a future to completion.

The program (conceptual walkthrough)

//! Minimal Tokio runtime entry point with `#[tokio::main]`. // 1. Configure the Tokio runtime to use multiple threads, // explicitly setting the background pool to exactly 2 worker threads. #[tokio::main(flavor = "multi_thread", worker_threads = 2)] // 2. Define the main entry point as an asynchronous function, // allowing the use of the `.await` keyword inside its body. async fn main() { // 3. Print the startup message to the console to signal that // the multi-threaded runtime has successfully initialized. println!("Tokio multi-thread runtime started"); // 4. Pause execution for 100 milliseconds. // Unlike standard thread::sleep, this non-blocking sleep yields control // back to the Tokio scheduler, allowing worker threads to process // other tasks while waiting. tokio::time::sleep(std::time::Duration::from_millis(100)).await; // 5. Print the completion message once the 100ms timer expires // and the execution resumes. println!("Done"); } // 6. The main function scope ends, safely shutting down the runtime and exiting.
Rust
복사

What #[tokio::main] expands to (simplified)

Roughly, the macro generates:
1.
A normal fn main().
2.
A Runtime::new() (with options you pass to the macro).
3.
runtime.block_on(async { ... your main body ... }).
So you do not get magic threads inside main itself — you get a runtime that polls async tasks until they complete or park waiting for I/O/time.

async fn main

The body of main is a future. Every .await is a point where the runtime may:
Run other tasks on worker threads, or
Park this task until a timer/I/O event wakes it.
Here, sleep(100ms).await yields the task; the runtime schedules other work (none in this tiny program), then resumes after the timer fires.

Macro options used in this example

Option
Meaning
flavor = "multi_thread"
Thread pool of worker threads runs tasks (good default for servers).
worker_threads = 2
Exactly two workers (default is num_cpus).
Alternative: #[tokio::main(flavor = "current_thread")] — all tasks run on the thread that calls block_on. Useful for tests or single-threaded embedded-style loops.

Mental model: executor + reactor

Tokio combines:
Executor — runs task futures when they are ready.
Reactor — OS event notification (epoll/kqueue/IOCP) for sockets, and a timer wheel for sleep / interval.
When you .await on a sleep, the task is not blocking an OS thread; it is removed from the runnable queue until the timer fires.

Common mistakes

Mistake
Why it hurts
Calling std::thread::sleep in async code
Blocks a worker thread; stalls other tasks.
Creating many Runtime::new() in a library
Prefer one runtime per process or use Handle::current().
Heavy CPU work in async fn without spawn_blocking
Starves the executor (see lesson 10).

Exercises

1.
Change worker_threads to 1 and observe behavior (should still work for this program).
2.
Switch to current_thread flavor — does output change? Why or why not?
3.
Add a second sleep and a spawn that prints between them — preview of tomorrow

안녕하세요

관련 기술 문의와 R&D 공동 연구 사업 관련 문의는 “glory@keti.re.kr”로 연락 부탁드립니다.

Hello

For technical and business inquiries, please contact me at “glory@keti.re.kr”