生产者消费者问题(Java和go实现)
介绍
维基百科介绍如下:
生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(Bounded-buffer problem),是一个多进程同步问题的经典案例。该问题描述了共享固定大小缓冲区的两个进程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
解决思路
要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。
通常采用信号量或者加锁的情况,如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形。
具体实现
具体的实现方式多种多样,而且每种语言都有各自己实现,这里分别给出Java和go实现。
Java实现方式是BlockingQueue阻塞队列,其他还有wait() / notify(),await() / signal(),信号量,管道等实现等。
go实现采用原生的goroutine和channel,其他还有sync.WaitGroup实现等。
Java实现
生产者
package com.demo.service;
import java.util.concurrent.BlockingQueue;
// 消费者
public class Consumer implements Runnable {
private final BlockingQueue<String> queue;
public Consumer(BlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
//
String product = queue.take();
System.out.println("【消费者】消费" + product + ",现库存" + queue.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
消费者:
package com.demo.service;
import java.util.concurrent.BlockingQueue;
// 生产者
public class Producer implements Runnable {
private final BlockingQueue<String> queue;
public Producer(BlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
System.out.println("【生产者】生产1件【产品】,现库存" + queue.size());
try {
queue.put("【产品】");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试类
package com.demo.service;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class Test {
// 创建大小为10的有界阻塞队列
private static BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(10);
public static void main(String[] args) {
// 创建生产者
Producer producer = new Producer(blockingQueue);
// 创建消费者
Consumer consumer = new Consumer(blockingQueue);
// 开启线程
new Thread(consumer).start();
new Thread(producer).start();
}
}
控制台输出:
...
【生产者】生产1件【产品】,现库存8
【生产者】生产1件【产品】,现库存3
【生产者】生产1件【产品】,现库存4
【生产者】生产1件【产品】,现库存5
【生产者】生产1件【产品】,现库存6
【生产者】生产1件【产品】,现库存7
【生产者】生产1件【产品】,现库存8
【消费者】消费【产品】,现库存2
【消费者】消费【产品】,现库存8
【消费者】消费【产品】,现库存7
【消费者】消费【产品】,现库存6
【消费者】消费【产品】,现库存5
【消费者】消费【产品】,现库存4
【消费者】消费【产品】,现库存3
【生产者】生产1件【产品】,现库存9
【生产者】生产1件【产品】,现库存3
【消费者】消费【产品】,现库存2
【消费者】消费【产品】,现库存3
【生产者】生产1件【产品】,现库存4
【生产者】生产1件【产品】,现库存3
...
go实现
package main
import (
"fmt"
)
// 生产者
func producer(channel chan<- string) {
for {
// 将产品发送到通道
channel <- "【产品】"
fmt.Printf("【生产者】生产1件【产品】,现库存 %d \n", len(channel))
}
}
// 消费者
func consumer(channel <-chan string) {
// 不停的消费产品
for {
// 从通道中取出数据,此处会阻塞直到信道中返回数据
product := <-channel
fmt.Printf("【消费者】消费 %s ,现库存 %d \n", product, len(channel))
}
}
func main() {
// 创建一个字符串类型长度为10的有缓冲通道
channel := make(chan string, 10)
// 创建producer函数的并发goroutine
go producer(channel)
go consumer(channel)
// 防止主协程退出
select {}
}
控制台
...
【消费者】消费 【产品】 ,现库存 5
【消费者】消费 【产品】 ,现库存 4
【消费者】消费 【产品】 ,现库存 3
【消费者】消费 【产品】 ,现库存 2
【消费者】消费 【产品】 ,现库存 1
【消费者】消费 【产品】 ,现库存 0
【生产者】生产1件【产品】,现库存 8
【生产者】生产1件【产品】,现库存 0
【生产者】生产1件【产品】,现库存 1
【生产者】生产1件【产品】,现库存 2
【生产者】生产1件【产品】,现库存 3
【生产者】生产1件【产品】,现库存 4
【生产者】生产1件【产品】,现库存 5
【生产者】生产1件【产品】,现库存 6
【生产者】生产1件【产品】,现库存 7
【生产者】生产1件【产品】,现库存 8
【生产者】生产1件【产品】,现库存 9
【生产者】生产1件【产品】,现库存 10
【消费者】消费 【产品】 ,现库存 0
【消费者】消费 【产品】 ,现库存 10
【消费者】消费 【产品】 ,现库存 9
【消费者】消费 【产品】 ,现库存 8
【消费者】消费 【产品】 ,现库存 7
【消费者】消费 【产品】 ,现库存 6
【消费者】消费 【产品】 ,现库存 5
【消费者】消费 【产品】 ,现库存 4
...