Python中使用Queue
实现多个线程之间的通讯。
例如,现在要构建一个照片处理系统,其分为三个阶段:download、resize、和upload
阶段。属于典型的生产者-消费者模型
。
首先,创建一个自定义的队列数据结构MyQueue
。
class MyQueue(object):
'''自定义队列结构
'''
def __init__(self):
self.items = deque()
self.lock = Lock() #锁
###放入照片
def put(self,item):
with self.lock:
self.items.append(item)
###取出照片
def get(self):
with self.lock:
return self.items.popleft()
对于上述三个阶段,定义Worker
线程,会从MyQueue
队列中取出待处理的任务,并针对该任务运行相关的函数,然后把运行结果放到另一个MyQueue
队列中。
class Worker(Thread): ###继承自Thread线程
def __init__(self,func,in_queue,out_queue):
super().__init__()
self.func = func #处理函数
self.in_queue = in_queue #输入队列
self.out_queue = out_queue #输出队列
self.polled_count = 0 #计数
self.work_done = 0 #处理计数
#线程调用此方法
def run(self):
while True: #死循环
self.polled_count += 1
try:
item = self.in_queue.get()
except IndexError:
sleep(0.1)
else:
result = self.func(item)
self.out_queue.put(result)
self.work_done += 1
现在,创建相关的队列,然后根据队列与工作线程之间的对应关系,把三个阶段拼接好。
download_queue = MyQueue()
resize_queue = MyQueue()
upload_queue = MyQueue()
done_queue = MyQueue()
threads = [
Worker(dowload,download_queue,resize_queue),
Worker(resize,resize_queue,upload_queue),
Worker(upload,upload_queue,done_queue)
]
启动线程,
for thread in threads:
thread.start()
for _ in range(1000):
download_queue.put(object())
上述流程,虽然可以正常工作,但是存在很多缺陷:
Worker
的run
方法处于死循环状态,无法终止。- 要想查询任务的进展,必须在最后
done_queue
上轮询查询已完成的任务数量是否达到指定任务数。 - 如果管线的某个阶段发生迟滞,那么随时都可能导致程序崩溃。比如,存在某个阶段处理的非常快,导致后续的流程来不及处理,就会导致程序不断的收到大量数据而耗尽内存。
为此,Python提供的Queue
类可以解决上述问题,而不是采用自定义的MyQueue
类。它具备阻塞式的队列操作,能够指定缓冲区尺寸,而且还支持join方法
。下面用Queue类实现相关逻辑。
class ClosableQueue(Queue):
SENTINEL = object()
def close(self):
self.put(self.SENTINEL)
def __iter__(self):
while True:
item = self.get()
try:
if item is self.SENTINEL:
return
yield item
finally:
self.task_done()
##根据ClosableQueue的定义修改工作线程
class StoppableWorker(Thread):
def __init__(self,func,in_queue,out_queue):
###...
def run(self):
### 只要for循环结束,线程就会退出
for item in self.in_queue:
result = self.func(item)
self.out_queue.put(result)
Queue
类使用的优点:
1.Queue
类使得工作线程无需再频繁的查询输入队列状态,其get
方法会持续阻塞,直到有新的数据加入;
2.管线的迟滞问题,Queue
类限定队列中最大处理的任务数量解决;
3.同时,Queue
类的task_done
方法可以追踪工作进度。有了这个方法,就不需要在管线末端的done_queue
处进行轮询了。
4.生产者只需要在Queue
实例上调用join
方法,并等待in_queue
结束即可。