8. 生产者-消费者
概念
生产者和消费者速度不会永远一样。中间放一个有界队列,生产者快了就在队列满时等待,消费者快了就在队列空时等待。两边只依赖队列,不直接调用对方。 这是后面消息队列、日志采集和任务调度的最小模型。队列容量是背压:容量用尽时,生产者停下来,而不是无限堆积任务。
- 一个队列,一端 put,另一端 take。
- Java 使用 ArrayBlockingQueue,Python 使用 queue.Queue(maxsize=...)。
- 用一个约定对象,例如 null 或 None,通知消费者退出。
- 队列本身负责等待和唤起,业务代码不必再写 wait / notify。
算法流程
下面的 UML 图说明 Java 例程和 Python 例程共同实现的流程。实线箭头表示调用或发送,虚线箭头表示返回或唤起。
有界队列交接(UML 时序图)
sequenceDiagram
participant Producer as 生产者
participant Queue as 有界队列
participant Consumer as 消费者
Producer->>Queue: put 数据
alt 队列已满
Queue-->>Producer: 生产者休眠,直到有空位
end
Consumer->>Queue: take
alt 队列为空
Queue-->>Consumer: 消费者休眠,直到有数据
end
Queue-->>Consumer: 返回一份数据,只被取走一次
Producer->>Queue: put 结束标记
Consumer->>Queue: take 到结束标记后退出
Java 例程
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class SimpleQueue {
public static void main(String[] args) {
// 容量只有 2。生产者比消费者快时,第 3 个 put 会停住,这就是背压
final BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(2);
Thread producer = new Thread(new Runnable() {
public void run() {
try {
for (int i = 1; i <= 5; i++) queue.put(i); // 满了就 WAITING,直到消费者 take
queue.put(-1); // 用负数通知消费者可以退出
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
});
Thread consumer = new Thread(new Runnable() {
public void run() {
try {
while (true) {
int n = queue.take(); // 空了就 WAITING,直到生产者 put
if (n < 0) break; // 结束标记不是业务数据
System.out.println("got " + n);
}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
});
producer.start(); // 两边只通过队列交接,不直接调用对方
consumer.start();
}
}
Python 例程
import threading
from queue import Queue
q = Queue(maxsize=2) # 有界。满了 put 会阻塞,空了 get 会阻塞
def producer():
for i in range(1, 6):
q.put(i) # 队列里已经有 2 个还没被取走时,生产者停在这里
q.put(None) # None 只作为结束标记,消费者看到就退出
def consumer():
while True:
item = q.get() # 没有数据时休眠,不需要自己写 wait
if item is None:
break
print("got", item) # 一份数据只被这一个消费者取走
threading.Thread(target=producer).start()
threading.Thread(target=consumer).start()