Course contentsShow
← Rust
Lesson 1108 of 1,91010. Concurrency PrimitivesFree lesson

Multi-Producer Single-Consumer Channels

You will pipe data from various worker threads into a single aggregation point.

In this lesson, you will learn how to synchronize data flow from multiple concurrent worker threads into a single aggregation point using the Multi-Producer Single-Consumer (MPSC) channel pattern.

The Problem: Decentralized Aggregation

When spawning multiple threads to perform work, gathering their results manually can be cumbersome. If you use a Mutex to share a common vector for results, you introduce contention: every thread must wait for the lock to submit its data. MPSC channels offer a cleaner alternative based on the concept of Communicating Sequential Processes: don't share memory by locking, share memory by communicating.

The MPSC Architecture

An MPSC channel consists of a Sender (which can be cloned) and a single Receiver. You can hand a Sender to any number of worker threads, while keeping the Receiver in your main thread (or a dedicated manager thread).

When a worker completes a task, it "sends" the result into the channel. The channel acts as a synchronized queue, decoupling the producer threads from the consumer. The consumer then loops, pulling values out of the channel as they arrive.

use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();

    for i in 0..3 {
        let thread_tx = tx.clone();
        thread::spawn(move || {
            let result = format!("Message from thread {}", i);
            thread_tx.send(result).unwrap();
        });
    }

    // Drop the original sender so the receiver knows when to stop
    drop(tx);

    for received in rx {
        println!("Got: {}", received);
    }
}

Why this is powerful

  1. Ownership Transfer: When you send a value, ownership moves from the producer to the channel. This prevents data races because the producer can no longer touch the data once sent.
  2. Backpressure: Channels can be buffered (using sync_channel), allowing you to restrict how many messages sit in the queue, preventing memory exhaustion if producers are faster than the consumer.
  3. Graceful Exit: By dropping all Sender instances, the Receiver loop will naturally exhaust the queue and terminate, providing a clean synchronization point for your program.

Key Takeaway: Use MPSC channels to shift from complex lock management to a message-passing architecture, allowing multiple producers to stream data safely into a single collector without explicit shared-state synchronization.