并发编程经典问题之生产者消费者。本文简单介绍了问题模型,并提供了一种 Python3 的解决方式。
问题模型
- 问题描述:
- 生产者-消费者模型描述的是有一群生产者进程在生产产品,并将这些产品提供给消费者进程并发进行,具备并发程序的典型特征。
- 为使生产者进程和消费者进程并发进行,在它们之间设置一个具有多个缓冲区的缓冲池,生产者进程可以将其所生产的产品放入一个缓冲区中,消费者进程可以从一个缓冲区中取得产品去消费。
- 问题分析:
- 所有的生产者进程和消费者进程的工作互相独立,是以异步方式运行的。
- 产者和消费者又是一个相互协作的关系,只有生产者生产之后,消费者才能消费,他们也是同步关系。
Talk is cheap, show me code
- 各种情况:
- 多个生产者,一个消费者:例如日志记录,产生日志的线程(进程)会有很多,但是真正写日志文件的线程(进程)只能有一个;
- 一个生产者,多个消费者:例如任务分发,接收任务只需要一个线程(进程),但是执行任务需要多个线程(进程)并发执行;
- 多个生产者,多个消费者。
- 以一个生产者,多个消费者为例,使用线程安全的
queue.Queue
作为缓冲区来实现 - queue 模块实现多生产者,多消费者队列。当信息必须安全的在多线程之间交换时,它在线程编程中是特别有用的。此模块中的 Queue 类实现了所有锁定需求的语义。
- queue 依赖于 Python 支持的线程可用性。
- queue 模块实现了三种类型的队列,它们的区别仅仅是条目取回的顺序:
- 在 FIFO 队列(Queue)中,先添加的任务先取回;
- 在 LIFO 队列(LifoQueue)中,最近被添加的条目先取回 (操作类似一个堆栈);
- 优先级队列(PriorityQueue)中,条目将保持排序(使用 heapq 模块)并且最小值的条目第一个返回。
from threading import Thread,current_thread
from queue import Queue
from time import sleep
from random import randint
PARALLEL_NUM = 5
MAX_WEIGHT = 4
# 任务类
class Task():
__uid = 0
__weight = 0
def __init__(self, version, weight):
self.__uid = version*100 + weight
self.__weight = weight
def __str__(self):
return str(self.__uid)
def run(self):
sleep(self.__weight)
# 生产者
def producer(q):
th = current_thread()
print("Producer[" + th.name + "] Started")
version = 1
sleep(1)
# 开始工作
while True:
batch = randint(1, MAX_WEIGHT)
print("producer put batch: " + str(version*100 + batch)) # 汇报版本信息
for weight in range(1, batch+1):
q.put(Task(version, weight))
sleep(1)
version += 1
# 消费者
def consumer(q):
th = current_thread()
print("Consumer[" + th.name + "] Started")
sleep(1)
# 开始工作
while True:
task = q.get() # 获取任务
task.run() # 执行任务
print(th.name + " finish task: " + str(task)) # 汇报结果
def main():
# 初始化队列
q = Queue(maxsize=100)
# 启动生产者线程
thread_producer = Thread(target=producer, args=(q,), daemon=True)
thread_producer.start()
# 启动消费者线程
thread_consumers = []
for i in range(PARALLEL_NUM):
thread_consumers.append(Thread(target=consumer, args=(q,), daemon=True))
thread_consumers[-1].start()
input("\n\n==Enter Any Key To Exit==\n\n")
if __name__ == '__main__':
main()