A runtime lets you do more than await one Future at a time. tokio::spawn, joining, and join! are the tools for running many async tasks concurrently — the async analogues of threads and channels.
spawn: concurrent tasks
tokio::spawn takes a Future and runs it as an independent task on the runtime, returning a handle you can .await for its result. Unlike a thread, a task is cheap — the runtime multiplexes thousands onto a few threads. Spawning is how you get true concurrency: several tasks making progress while each waits on its own I/O.
Awaiting many at once
To wait for several Futures together, tokio::join! drives them concurrently and waits for all to finish — far faster than awaiting them one after another, because their waits overlap. tokio::select! instead waits for whichever Future finishes first, useful for timeouts and racing operations.
tokio::join! runs them concurrently and finishes in ~1. When tasks are independent, joining is the difference between concurrent and merely sequential async.Tasks usually must be Send
Because tokio may move a task between worker threads, spawned Futures generally must be Send — the same trait from the Concurrency track. So the Arc/Mutex rules apply here too: share state across tasks with Arc, and the compiler keeps async sharing race-free just as it does for threads.