Rust笔记 - "channel多生产者单消费者/多生产者多消费者" 模型

3,548 阅读2分钟

Rust 标准库提供了mpsc::channel工具,这是一个多生产者单消费者的通道,表明Rust原生支持多生产者单消费者模型。多生产者多消费者模型则需要我们自己实现。这两个模型有什么好处呢?Rust号称是并发安全的语言,这两个模型当然是奔着并发的目的去的。这两个模型的不同点是:消费者的数量不同。如果程序的消费行为比生产行为快很多,使用单消费者模型是没问题的,也不会造成消费队列积压。但多消费者模型更加通用一些,单消费者模型能满足的场景,多消费者模型也能满足。


Rust单消费者实例:

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

fn main() {
    let mut tv = vec![];
    let (tx, rx) = mpsc::channel();

    for i in 0..5 {
        let tx = tx.clone();
        let hdl = thread::spawn(move || {
            let message = format!("I am thread: {}", i);
            tx.send(message).unwrap();
        });
        tv.push(hdl);
    }

    for _i in 0..5 {
        let message = rx.recv().unwrap();
        println!("main thread recv: {}", message);
    }

    for hdl in tv {
        hdl.join().unwrap();
    }
}

输出:

main thread recv: I am thread: 0
main thread recv: I am thread: 2
main thread recv: I am thread: 1
main thread recv: I am thread: 3
main thread recv: I am thread: 4

上面的代码通过五个子线程(生产者)通过通道发送信息,主线程(消费者)接收通道信息,实现多生产者单消费者模型


Rust多消费者实例:

use std::sync::{mpsc, Arc, Mutex};
use std::thread;

fn is_prime(n: usize) -> bool {
    (2..n).all(|i| n % i != 0)
}

fn producer(id: usize, tx: mpsc::Sender<(usize, usize)>) -> thread::JoinHandle<()> {
    thread::spawn(move || {
        for i in (id - 1) * 100 + 1..id * 100 {
            tx.send((id, i)).unwrap();
        }
    })
}

fn worker(
    id: usize,
    shared_rx: Arc<Mutex<mpsc::Receiver<(usize, usize)>>>,
) -> thread::JoinHandle<()> {
    thread::spawn(move || loop {
        let rx = shared_rx.lock().unwrap();
        if let Ok((i, n)) = rx.recv() {
            if n != 0 && is_prime(n) {
                println!("worker {} found a prime: {} in producer {}", id, n, i);
            }
        } else {
            return ();
        }
    })
}

fn main() {
    let mut producer_v = vec![];
    let mut worker_v = vec![];
    let (tx, rx) = mpsc::channel();
    let shared_rx = Arc::new(Mutex::new(rx));

    for i in 1..4 {
        let hdl = worker(i, shared_rx.clone());
        worker_v.push(hdl);
    }

    for i in 1..3 {
        let hdl = producer(i, tx.clone());
        producer_v.push(hdl);
    }

    drop(tx);

    for hdl in producer_v {
        hdl.join().unwrap();
    }

    for hdl in worker_v {
        hdl.join().unwrap();
    }
}

输出:

...
worker 2 found a prime: 41 in producer 1
worker 1 found a prime: 47 in producer 1
worker 3 found a prime: 137 in producer 2
worker 3 found a prime: 53 in producer 1
worker 2 found a prime: 59 in producer 1
worker 2 found a prime: 139 in producer 2
...

上面的例子通过多个生产者生成数值发送到通道,多个消费者从通道中获取数据计算是否素数,如果是素数就打印信息。通过这个简单的例子实现了多生产者多消费者模型,实现多消费者的关键点是,通过ArcMutex实现Receiver能够在线程间安全的共享。

题外话

  • 线程池一般采用多消费者模型,原因是,多消费者模型能更好的利用多核cpu的优势。
  • 虽然单消费者模型没有多消费者模型通用(强大),但其逻辑简单,代码出bug的概率也低一些。