Theme
Q073. How do Tokio channels provide communication and backpressure?
Short Answer
Tokio channels transfer values between tasks and can replace shared mutable state with explicit message ownership. A bounded mpsc channel provides backpressure: once capacity is full, send().await waits until the receiver makes space. oneshot sends one result, watch publishes the latest value, and broadcast lets every active subscriber receive each value subject to lag handling. Choose by delivery semantics, not convenience.
Deep Dive
Channels define both communication and lifecycle:
mpsc: many senders, one receiver; bounded capacity controls producer pressure;oneshot: exactly one value, commonly request-response completion;watch: receivers observe the most recent state and may skip intermediate updates;broadcast: each subscriber observes messages, while slow subscribers can lag and lose old buffered values.
An unbounded queue removes producer waiting but can convert overload into unbounded memory growth. Capacity should reflect a deliberate buffering and latency budget. Backpressure must propagate far enough upstream; spawning another task merely to wait on every blocked send defeats it.
Closing senders or receivers is also a control signal. Production code should handle SendError, end-of-stream, lag, and shutdown rather than unwrap them blindly.
Internal Model
A bounded channel stores messages in a synchronized queue and tracks waiting senders and receivers. Sending may return Pending when no slot exists; receiving or closing wakes relevant waiters.
Values move through the queue. Shared ownership is needed only when the message itself must be retained by multiple parties.
Example
rust
use tokio::sync::mpsc;
#[tokio::main]
async fn main() {
let (sender, mut receiver) = mpsc::channel(1);
let producer = tokio::spawn(async move {
sender.send(20_u64).await.unwrap();
sender.send(22_u64).await.unwrap();
});
let first = receiver.recv().await.unwrap();
let second = receiver.recv().await.unwrap();
producer.await.unwrap();
assert_eq!(first + second, 42);
}Common Mistakes
- Using an unbounded channel without a memory or overload policy.
- Assuming
watchdelivers every intermediate value. - Hiding blocked sends behind unlimited spawned tasks.
Follow-ups
- How should channel capacity be selected and monitored?
- What happens when a
broadcastreceiver lags? - When is a channel clearer than
Arc<Mutex<T>>?
Related
- Q042. When should you use
Arc,Mutex,RwLock, atomics, or message passing? - Q049. How do deadlocks arise, and how can Rust code prevent them?
- Q074. How do you implement graceful shutdown and task supervision?
References
- Tokio Documentation
- Tokio Tutorial