#281 Jul 25, 2026

281. mpsc::channel — Send Results Back Instead of Fighting Over a Mutex

Threads that only produce values don’t need shared state. A channel turns “everyone locks the accumulator” into “everyone mails in their answer.”

This morning’s bite 280 collected thread results through a Mutex, then peeled it off with into_inner. That works — but every worker contends for the same lock, and the value only exists to be fought over:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
use std::sync::Mutex;
use std::thread;

let data = [1, 2, 3, 4, 5, 6];
let total = Mutex::new(0);

thread::scope(|s| {
    for chunk in data.chunks(2) {
        s.spawn(|| {
            *total.lock().unwrap() += chunk.iter().sum::<i32>()
        });
    }
});
let sum = total.into_inner().unwrap();

With std::sync::mpsc, each worker gets its own Sender and the results flow one way:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use std::sync::mpsc;

let (tx, rx) = mpsc::channel();

thread::scope(|s| {
    for chunk in data.chunks(2) {
        let tx = tx.clone();
        s.spawn(move || {
            tx.send(chunk.iter().sum::<i32>()).unwrap()
        });
    }
    drop(tx); // keep this — or the next line waits forever

    let sum: i32 = rx.iter().sum();
    assert_eq!(sum, 21);
});

No lock, no poisoning, no unwrap on contention that can’t happen. Receiver implements Iterator, so collecting results is just rx.iter() — it yields each value as it arrives and ends when every Sender is gone.

That last part is the classic trap: the original tx counts too. Each worker got a clone, but the one you cloned from is still alive in the parent — if you don’t drop(tx), the channel never closes and rx.iter() blocks forever after the last worker exits. Symptom: all threads finished, program hangs.

mpsc is multi-producer, single-consumer: clone Sender freely, but there’s exactly one Receiver — which is why receiving needs no lock at all. Sends are non-blocking and buffered; if you want backpressure, mpsc::sync_channel(n) blocks senders once n messages are queued.

Mutexes are for threads that share data. When they only hand data back, give them a channel.

In std since Rust 1.0.

← Previous 280. Mutex::get_mut — You Have &mut, Stop Paying for lock() Next → 282. mpsc::sync_channel — A Channel That Pushes Back When the Consumer Falls Behind