Concurrency Patterns
Concurrency lets a program make progress on multiple tasks without waiting on each one sequentially — essential for responsive UIs, high-throughput servers, and using multi-core hardware. But it's also where some of the hardest bugs live: race conditions and deadlocks that appear only under specific timing.
The first key distinction: concurrency is about structure (dealing with many things at once), while parallelism is about execution (doing many things at once). Async I/O gives you concurrency on a single core; parallelism needs multiple cores.
TL;DR
- Concurrency ≠ parallelism — structure vs simultaneous execution.
- Prefer async for I/O-bound tasks, parallelism for CPU-bound tasks.
- Avoid shared mutable state — it's the source of races and deadlocks.
- Use higher-level abstractions (pools, channels,
Promise.all) over raw threads.
Quick Example
Go's CSP model — communicate over a channel instead of sharing memory:
Core Concepts
Concurrency vs parallelism
- Concurrency — dealing with multiple things at once (structuring work so it can interleave).
- Parallelism — actually doing multiple things at once (multiple cores executing simultaneously).
Models
- Threads — OS-level, shared memory (needs locks).
- Async / event loop — single-threaded, non-blocking (JavaScript, asyncio).
- Actors — isolated state, message passing (Erlang, Akka).
- CSP / channels — Go's goroutines and channels: "share memory by communicating."
Common problems
- Race condition — output depends on timing of unsynchronized access.
- Deadlock — tasks each wait on a resource the other holds.
- Starvation — a task never gets the resources it needs.
Patterns by Language
Python — threads for I/O, processes for CPU
Go — goroutines, channels, worker pools
JavaScript — concurrent awaits, Web Workers for CPU
Best Practices
- Avoid shared mutable state; when you must share, guard it with a lock or use message passing.
- Pick the model by workload: async for I/O, parallel processes/threads for CPU.
- Use higher-level abstractions (
ThreadPoolExecutor, worker pools,Promise.all) over hand-rolled threads. - Always set timeouts and cancellation so stuck tasks don't hang the system.
Common Mistakes
Unguarded shared state
Using threads for CPU-bound Python work
FAQ
What's the difference between concurrency and parallelism?
Concurrency is structuring a program to handle multiple tasks that can make progress independently (interleaving); parallelism is literally running them at the same time on multiple cores. You can have concurrency on a single core (async I/O); parallelism requires multiple cores.
Async or threads or processes?
Use async (event loop) for I/O-bound work — many waiting tasks on one thread. Use threads for I/O concurrency in languages without the GIL constraint, and processes for CPU-bound work (especially in Python, to bypass the GIL). Match the tool to whether you're waiting on I/O or burning CPU.
How do I avoid race conditions and deadlocks?
Minimize shared mutable state; prefer message passing (channels) or immutable data. When you must share, protect it with locks acquired in a consistent order (to avoid deadlock), keep critical sections short, and consider higher-level concurrency primitives that encapsulate the synchronization.
Why is Go's concurrency model praised?
Goroutines are cheap (you can run thousands), and channels encourage "share memory by communicating" rather than locking shared state — which sidesteps a whole class of bugs. Combined with context for cancellation, it makes concurrent code approachable. See Go.
Related Topics
- Go — Goroutines and channels
- Async JavaScript — The event loop and Promises
- Python — The GIL, threads, and multiprocessing
- Rust — Fearless concurrency via ownership
- Message Queues — Concurrency across services