mpsc in Rust: Bounded vs Unbounded Channels

Max WellsMax WellsFounder of Rustify

TL;DR: mpsc stands for multi-producer, single-consumer. It is a channel where many senders can push values and one receiver pulls them. Use std::sync::mpsc for synchronous/threaded code and tokio::sync::mpsc for async code. Create a channel with mpsc::channel() (unbounded) or mpsc::bounded(n) / mpsc::channel(n) (bounded). Clone the Sender to create additional producers. The channel closes when all senders are dropped.


What Is an mpsc Channel?

An mpsc channel is a queue that lets multiple threads or tasks send values to a single receiver without shared mutable state.

use std::sync::mpsc;
use std::thread;
 
let (tx, rx) = mpsc::channel();
 
for i in 0..5 {
    let tx = tx.clone();
    thread::spawn(move || {
        tx.send(i).unwrap();
    });
}
 
drop(tx); // drop the original sender so rx knows when all are done
 
for val in rx {
    println!("received: {val}");
}

Cloning tx gives each thread its own sender. The for val in rx loop ends when all senders are dropped.


How Do You Use tokio::sync::mpsc?

For async code, use tokio::sync::mpsc; it provides .send().await and .recv().await that yield instead of blocking.

use tokio::sync::mpsc;
 
#[tokio::main]
async fn main() {
    let (tx, mut rx) = mpsc::channel(32); // bounded: buffer of 32
 
    tokio::spawn(async move {
        for i in 0..10 {
            tx.send(i).await.unwrap();
        }
    });
 
    while let Some(val) = rx.recv().await {
        println!("received: {val}");
    }
}

The channel(32) creates a bounded channel. If the buffer fills, .send().await will yield until space is available; applying backpressure automatically.


Bounded vs Unbounded Channels

Bounded channels apply backpressure; unbounded channels can grow without limit and risk out-of-memory.

Bounded (channel(n))Unbounded (unbounded_channel())
BufferFixed capacity nGrows indefinitely
Send blocks?Yes, when fullNever
Backpressure✅ Automatic❌ None
Best forProduction servicesBurst-tolerant pipelines

Prefer bounded in production; unbounded channels hide backpressure bugs until you run out of memory under load.


How Does mpsc Differ From Other Channel Types?

mpsc is one-receiver-many-senders. Rust also offers broadcast (many receivers) and oneshot (single value).

ChannelSendersReceiversUse case
mpscManyOneWork queues, event aggregation
broadcastOneManyFan-out notifications
oneshotOneOneSingle response (request/reply)
watchOneManyLatest-value sharing

Frequently Asked Questions

tx.send() returns Err(SendError(val)). The value is returned to you so it is not lost.

Technically yes, but .recv() blocks the thread. Use tokio::sync::mpsc in async contexts; it integrates with the async runtime correctly.

No. For mpmc, use crossbeam::channel or flume (async-compatible).


Sources


  • channel: Overview of all channel types in Rust
  • tokio: The async runtime for tokio::sync::mpsc
  • send-sync: The traits that make channels thread-safe
  • select: Often combined with mpsc receivers
  • stream: mpsc receivers are often wrapped into streams for ergonomic async pipelines

Keep Reading

Ready to Land a $120k+ Rust Job in the US or Europe?