rust-sync
Synchronization is how concurrent programs coordinate independent work. In Rust, the type system prevents many unsafe data races, but an application still needs clear rules for shared state, message passing, task wakeups, and concurrency limits.
In Tokio, synchronization usually means coordinating asynchronous tasks. A task may run on the same OS thread as another task, or it may move between worker threads. Because of that, Tokio provides async-aware primitives in tokio::sync [1]. They look similar to standard synchronization tools, but their waiting operations are designed to yield back to the async runtime instead of blocking the thread.
The first decision is whether the tasks should share state or communicate by sending values.
Message passing
Message passing is often used to coordinate tasks in async Rust because it avoids shared mutable state. Instead of many tasks locking the same object, one task can own the state while other tasks send requests to it.
| Primitive | Pattern | Use it when |
|---|---|---|
oneshot [2] |
one sender, one receiver, one value | A task needs to return exactly one result to another task. |
mpsc [3] |
many producers, single consumer | Many tasks send jobs, commands, or results into one worker loop. |
broadcast [4] |
many producers, many consumers, every receiver sees each message | All subscribers should receive each event, such as chat messages or pub/sub events. |
watch [5] |
many producers, many consumers, only the latest value is kept | Tasks only care about the newest state, such as config changes or shutdown signals. |
The difference between broadcast and watch is important: broadcast is for event streams where each message matters, while watch is for state updates where old values can be skipped.
Shared state
Shared state is useful when several tasks truly need access to the same data structure. In this case, synchronization protects the data while a task reads or mutates it.
| Primitive | Pattern | Use it when |
|---|---|---|
Mutex<T> [6] |
one task accesses the value at a time, whether reading or writing | The state must be mutated and the critical section is small. |
RwLock<T> [7] |
many readers or one writer | Reads are frequent, writes are less common, and read concurrency matters. |
OnceCell<T> [8] |
initialize a value once, then share it | A global or shared async value should be created lazily one time. |
Mutex does not distinguish between read access and write access. Holding the lock means the task has exclusive access to the value, even if it only reads the value.
Mutex is simpler than RwLock. Use RwLock only when the extra read/write distinction is actually useful; otherwise a Mutex is easier to reason about.
RwLock allows many readers at the same time, but writers are exclusive. This means one writer and many readers cannot hold the lock together. If readers are still holding the read lock, the writer must wait.
Coordination without sharing data
Some synchronization is not about moving values or protecting a data structure. It is only about controlling when tasks are allowed to continue.
| Primitive | Pattern | Use it when |
|---|---|---|
Semaphore [9] |
a fixed number of permits | Limit concurrency, such as allowing only N requests or jobs at once. |
Notify [10] |
wake one or more waiting tasks without sending data | A task only needs a signal that something changed. |
Barrier [11] |
multiple tasks wait until all reach the same point | A group of tasks must start the next phase together. |
Barrier is useful when a group of tasks must rendezvous before continuing. For example, Barrier::new(3) waits until three tasks call wait().await; when the third task arrives, all waiting tasks are released and can enter the next phase together [11].
This note focuses on choosing the right Tokio synchronization primitive before writing the code. The goal is not to lock everything, but to match the primitive to the communication pattern.
References
[2] Tokio oneshot
[3] Tokio mpsc
[4] Tokio broadcast
[5] Tokio watch
[6] Tokio Mutex
[7] Tokio RwLock
[8] Tokio OnceCell
[9] Tokio Semaphore
[10] Tokio Notify
[11] Tokio Barrier