Search
moon
sun
πŸ¦€

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”