第39條 用Queue來協調各個線程之間的工作

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())

上述流程,雖然可以正常工作,但是存在很多缺陷:

  • Workerrun方法處於死循環狀態,無法終止。
  • 要想查詢任務的進展,必須在最後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結束即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章