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
...
上面的例子通过多个生产者生成数值发送到通道,多个消费者从通道中获取数据计算是否素数,如果是素数就打印信息。通过这个简单的例子实现了多生产者多消费者模型
,实现多消费者的关键点是,通过Arc
和Mutex
实现Receiver
能够在线程间安全的共享。
题外话
- 线程池一般采用多消费者模型,原因是,多消费者模型能更好的利用多核cpu的优势。
- 虽然单消费者模型没有多消费者模型通用(强大),但其逻辑简单,代码出bug的概率也低一些。