Concurrency, Parallelism, and Async: What's the Actual Difference?
Every process believes it's the only one that exists — kind of like you with that girl you like. The kernel lies to it. It makes the process believe it owns everything: RAM, disk, CPU, all of it, just for itself.
That's the illusion the OS keeps up for every single process on your machine. And it's a strict one — a process can't reach into another process's memory. They're sealed off from each other, each living in its own little bubble of "reality."
Threads are different. When you run a Rust program, it starts with one thread — the main thread, running your main function. From there, main can spawn more threads with std::thread::spawn(). But unlike processes, threads inside the same process share memory. No illusion needed — they're already in the same room.
That's the baseline we need before talking about concurrency, parallelism, and what the hell async actually is.
Concurrency is one thread, juggling multiple tasks. The tasks compete for the thread's attention. Only one runs at any given instant, but the thread switches between them.
Parallelism is multiple threads, each running its own task, actually happening at the same time.
Say you have 10 tasks, each taking exactly 1 second. Do them one after another on a single thread, and you're looking at 10 seconds. Now imagine you've got 9 friends to help — 10 people total, one task each. What took 10 seconds now takes 1, because everyone's working at the same time. That's parallelism: you're trading more workers for more speed.
For CPU-heavy work, parallelism is usually what you want. Break the job into pieces, hand them out, merge the results at the end.
But some tasks aren't CPU-bound — they're just waiting. Waiting for a network response, a disk read, whatever. And a thread that's waiting is a thread doing nothing, just sitting there blocked.
Spawning a new thread every time something blocks doesn't scale. This is where async comes in: instead of a thread freezing while it waits, you pause that task, move on to another one, and come back once the first one's unblocked.
In short: async lets a task step aside instead of blocking the thread.
Tokio, the runtime almost everyone reaches for, runs a work-stealing thread pool by default. Your async tasks aren't stuck on one thread being concurrent with each other — they get distributed across multiple threads, running in parallel, and within each of those threads, tasks are still concurrent with one another whenever they'd otherwise block.
You get both. At the same time. Without writing a single line of manual thread management.
Which is exactly why nobody spawns a thread per incoming request on a server anymore. The runtime already does the hard part — spreading work across threads, and keeping each thread busy instead of idle. You just write the async code and let it figure out the rest.