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
結束即可。