22.3 Threads vs. Async, and I/O-Bound vs. CPU-Bound Workloads

Choosing between OS threads or async tasks in Rust often depends on whether your workload is I/O-bound or CPU-bound.

22.3.1 Threads

Rust threads correspond to OS threads and get preemptively scheduled by the operating system. On multi-core systems, multiple threads can run in parallel; on single-core systems, they run concurrently via scheduling. Threads are generally well-suited for CPU-bound workloads because the OS can run them in parallel on multiple cores, potentially reducing overall computation time.

A thread can also block on a long-running operation (e.g., a file read) without stopping other threads. However, creating a large number of short-lived threads can be costly in terms of context switches and memory usage—so a thread pool is often a better choice for many small tasks.

Note: In Rust, a panic in a spawned thread does not necessarily crash the entire process; join() on that thread returns an error instead.

22.3.2 Async Tasks

Async tasks use cooperative scheduling. You define tasks with async fn, and they yield at .await points, allowing multiple tasks to share just a handful of OS threads. This is excellent for I/O-bound scenarios, where tasks spend significant time waiting on I/O; as soon as one task is blocked, another task can continue.

If an async task performs CPU-heavy work without frequent .await calls, it can block the thread it runs on, preventing other tasks from making progress. In such cases, you typically offload heavy computation to a dedicated thread or thread pool.

22.3.3 Matching Concurrency Models to Workloads

  • I/O-Bound:

    • Primarily waits on network, file I/O, or external resources.
    • Async shines here by letting many tasks efficiently share a small pool of threads.
    • Scales to large numbers of connections with minimal overhead.
  • CPU-Bound:

    • Spends most of the time in tight loops performing calculations.
    • OS threads or libraries like Rayon leverage multiple cores for genuine parallel speedups.
    • Parallelism can reduce overall computation time.

In real applications, you’ll often blend these models. A web server might use async for managing connections, plus threads or Rayon for heavy computations like image processing. In all cases, Rust enforces safe data sharing at compile time, helping you avoid typical multithreading errors.