Modern programs rarely run in isolation.
From web servers to simulations, systems need to handle many tasks at once — reading files, responding to users, updating shared state.
Concurrency models describe how those tasks cooperate safely while sharing time, memory, or messages.
Languages choose concurrency models to balance speed, safety, and simplicity.
Understanding them clarifies why Go uses goroutines, why Java still uses threads, and why Erlang can run telecom systems for decades without restarting.
Threads and Shared Memory
The threaded model is the most direct and oldest form of concurrency.
Multiple threads execute simultaneously within one process, sharing the same address space.
Key Idea
Each thread runs independently but can read and modify shared data.
To prevent interference, languages provide synchronization primitives such as locks, mutexes, and condition variables.
Example
Shared Counter
// Two threads increment a shared integerfor (int i = 0; i < 1000; i++) { lock(m); counter++; unlock(m);}
Without the lock, increments interleave unpredictably, losing updates — a classic race condition.
Advantages
Speed: direct memory access, zero copying.
Low latency: efficient for compute-heavy tasks.
Widely supported: core to C, C++, and Java runtimes.
Drawbacks
Races: two threads reading/writing the same variable unsafely.
Deadlocks: circular waits on locks.
Livelocks / starvation: threads spinning or never scheduled.
Non-determinism: difficult to reproduce or test.
Warning
In shared-memory systems, correctness depends on discipline, not guarantees.
One missing lock() or misplaced volatile can destabilize an entire program.
Lock-Based Coordination
Locks are simple but error-prone.
More sophisticated abstractions evolved to manage concurrency without explicit locking:
Monitors: integrate locks and condition variables into objects (e.g., Java synchronized).
Semaphores: countable access tokens; used for resource pools.
Barriers and latches: ensure groups of threads reach a point before proceeding.
Atomic operations: hardware-level primitives for lock-free algorithms.
Languages like Rust address safety through ownership and borrowing, which statically prevent certain races while still compiling to efficient machine code.
Actors and Message Passing
The actor model takes a different path: eliminate shared memory entirely.
Each actor is an independent computational entity with:
A local state (private data),
A mailbox for incoming messages,
The ability to spawn new actors and send messages.
Messages are immutable and asynchronous.
An actor processes one message at a time, guaranteeing isolation without locks.
Tip
“Share memory by communicating, not communicate by sharing memory.” — Go proverb
This Erlang actor handles additions safely because only it can modify its internal state.
Advantages
No data races by design.
Compositional: actors can be distributed across nodes.
Fault isolation: one actor’s failure doesn’t crash others.
Drawbacks
Message ordering: no guarantee across senders.
Latency: communication overhead.
Backpressure: unbounded queues can overflow.
Warning
Actor systems trade shared-memory bugs for coordination bugs.
Understanding delivery guarantees and queue behavior is crucial.
Communicating Sequential Processes (CSP)
A third model, CSP, connects independent processes through explicit channels.
Instead of shared memory or mailboxes, processes use synchronous or buffered sends and receives.
Go’s goroutines and channels, as well as Rust’s crossbeam, are modern CSP descendants.
Example
Go Channel Example
ch := make(chan int)go func() { ch <- 42 }()x := <-ch // waits for value
The send and receive synchronize automatically, avoiding explicit locks.
Key Principles
Happens-before relation: defines safe ordering of communication.
Select / alt: allows choice among multiple channel operations.
Determinism: well-structured channel programs are reproducible and analyzable.
CSP strikes a middle ground: it enforces communication discipline like actors but allows direct channel reasoning like threads.
Comparing Models
Feature
Threads + Locks
Actors
CSP
Memory sharing
Shared heap
Isolated mailboxes
Explicit channels
Synchronization
Locks, monitors
Message order
Channel operations
Safety
Manual discipline
Data-race free
Structured communication
Performance
Very fast
Moderate
Moderate
Reasoning
Difficult
Localized
Composable
Note
Most real languages blend these models.
C++20 adds atomic channels; Go actors use CSP channels internally; Akka and Orleans layer actor frameworks on thread pools.
Practical Guidelines
Keep shared state minimal.
Immutable data simplifies reasoning.
Acquire locks in a consistent global order.
Prevents deadlocks.
Use timeouts or non-blocking primitives.
Avoids starvation.
Model concurrency at the message or channel level, not the thread level.
Higher-level abstractions scale better.