Concurrency & performance
Concurrency exists to overlap waiting (I/O, locks, timers)—not because “more threads = faster.” This page summarizes model choice, failure modes, and a repeatable measurement loop.
Model comparison
| Model | Mechanism | Fits | Watch out |
|---|---|---|---|
| Threads + shared memory | OS threads, locks, cond vars | CPU parallelism, fine shared state | Deadlock, false sharing, priority inversion |
| Coroutines / async-await | Cooperative scheduling, event loop | High I/O concurrency (HTTP, DB) | Blocking calls stall the loop |
| Actors / messages | Mailboxes, no shared mutation | Distributed, state machines | Backpressure, ordering, timeouts |
| Data parallel | Shards, map-reduce, GPU | Batch, linear algebra | Split cost, memory bandwidth |
Swift: async/await, Task, actors; UI state on @MainActor.
TypeScript/Node: single-threaded loop; CPU work in workers or native code.
Go: goroutines + channels; watch leaks and unbuffered deadlocks.
Correctness: three pillars
- Mutual exclusion: one writer (or clear RW rules) for mutable state.
- Visibility: writes seen by other threads (memory model; avoid cargo-cult
volatile). - Ordering: happens-before via locks, channel ops, atomics.
warning
Data races: unsynchronized concurrent access is undefined behavior (Rust/Swift often reject at compile time; C/C++ may not). Prefer actors and type isolation over “one more mutex.”
Performance workflow
| Step | Action | Examples |
|---|---|---|
| 1. Metrics | p95 latency, throughput, errors, RSS | SLI/SLO, product telemetry |
| 2. Load | Production-like QPS, payloads, cold/warm cache | k6, wrk, custom scripts |
| 3. Profile | Hot functions and waits | Instruments, perf, py-spy |
| 4. Classify | CPU / alloc / locks / I/O / deps | Flame graphs + trace spans |
| 5. Change | One hypothesis at a time | Before/after + regression tests |
Fast path: remove waiting (batching, pools, cache, async I/O) before micro-optimizing CPU.
Anti-patterns
| Anti-pattern | Symptom | Fix |
|---|---|---|
| Huge thread pools | Context-switch blow-up | Size to cores + blocking ratio |
| Global mutable singletons | Rare crashes, torn reads | DI, actors, immutable snapshots |
| Blocking inside async | Latency spikes | Dedicated await APIs, offload pool |
| Unbounded queues | RSS growth, latency avalanches | Backpressure, rate limits, documented drops |
| Premature parallelism | Complexity ↑, gain ≈ 0 | Amdahl: profile serial bottleneck first |
Architecture tie-in
- Async boundaries need timeouts, cancellation, idempotency—see boundaries.
- Release cadence affects tolerance for concurrency bugs—see CI/CD.