我們經常會採用生產者/消費者關係的兩個線程來處理一個共用緩衝區的資料。例如一個生產者線程接受使用者資料放入一個共用緩衝區裡,等待一個消費者線程對資料取出處理。但是如果緩衝區的太小而生產者和消費者兩個非同步線程的速度不同時,容易出現一個線程等待另一個情況。為了儘可能的縮短共用資源並以相同速度工作的各線程的等待時間,我們可以使用一個“隊列”來提供額外的緩衝區。
建立一個“隊列”對象
import Queue
myqueue = Queue.Queue(maxsize = 10)
Queue.Queue類即是一個隊列的同步實現。隊列長度可為無限或者有限。可通過Queue的建構函式的選擇性參數maxsize來設定隊列長度。如果maxsize小於1就表示隊列長度無限。
將一個值放入隊列中
myqueue.put(10)
調用隊列對象的put()方法在隊尾插入一個項目。put()有兩個參數,第一個item為必需的,為插入項目的值;第二個block為選擇性參數,預設為1。如果隊列當前為空白且block為1,put()方法就使調用線程暫停,直到空出一個資料單元。如果block為0,put方法將引發Full異常。
將一個值從隊列中取出
myqueue.get()
調用隊列對象的get()方法從隊頭刪除並返回一個項目。選擇性參數為block,預設為1。如果隊列為空白且block為1,get()就使調用線程暫停,直至有項目可用。如果block為0,隊列將引發Empty異常。
我們用一個例子來展示如何使用Queue
#!/usr/bin/env python
import Queue
import threading
import urllib2
import time
hosts = ["http://yahoo.com", "http://google.com.hk", "http://amazon.com",
"http://ibm.com", "http://apple.com"]
queue = Queue.Queue()
class ThreadUrl(threading.Thread):
"""Threaded Url Grab"""
def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue
def run(self):
while True:
#grabs host from queue
host = self.queue.get()
#grabs urls of hosts and prints first 1024 bytes of page
url = urllib2.urlopen(host)
print url.read(1024)
#signals to queue job is done
self.queue.task_done()
start = time.time()
def main():
#spawn a pool of threads, and pass them queue instance
for i in range(5):
t = ThreadUrl(queue)
t.setDaemon(True)
t.start()
#populate queue with data
for host in hosts:
queue.put(host)
#wait on the queue until everything has been processed
queue.join()
main()
print "Elapsed Time: %s" % (time.time() - start)
在 Python 中使用線程時,這個模式是一種很常見的並且推薦使用的方式。具體工作步驟描述如下:
- 建立一個 Queue.Queue() 的執行個體,然後使用資料對它進行填充。
- 將經過填充資料的執行個體傳遞給線程類,後者是通過繼承 threading.Thread 的方式建立的。
- 產生守護線程池。
- 每次從隊列中取出一個項目,並使用該線程中的資料和 run 方法以執行相應的工作。
- 在完成這項工作之後,使用 queue.task_done() 函數向任務已經完成的隊列發送一個訊號。
- 對隊列執行 join 操作,實際上意味著等到隊列為空白,再退出主程式。
在使用這個模式時需要注意一點:通過將守護線程設定為 true,將允許主線程或者程式僅在守護線程處於活動狀態時才能夠退出。這種方式建立了一種簡單的方式以控製程序流程,因為在退出之前,您可以對隊列執行 join 操作、或者等到隊列為空白。
join()
保持阻塞狀態,直到處理了隊列中的所有項目為止。在將一個項目添加到該隊列時,未完成的任務的總數就會增加。當使用者線程調用 task_done() 以表示檢索了該項目、並完成了所有的工作時,那麼未完成的任務的總數就會減少。當未完成的任務的總數減少到零時,join() 就會結束阻塞狀態。