Queue 簡介
Queue 叫隊列,是數(shù)據(jù)結構中的一種,基本上所有成熟的編程語言都內(nèi)置了對 Queue 的支持。
Python 中的 Queue 模塊實現(xiàn)了多生產(chǎn)者和多消費者模型,當需要在多線程編程中非常實用。而且該模塊中的 Queue 類實現(xiàn)了鎖原語,不需要再考慮多線程安全問題。
該模塊內(nèi)置了三種類型的 Queue,分別是 class queue.Queue(maxsize=0),class queue.LifoQueue(maxsize=0) 和 class queue.PriorityQueue(maxsize=0)。它們?nèi)齻€的區(qū)別僅僅是取出時的順序不一致而已。
Queue 是一個 FIFO 隊列,任務按照添加的順序被取出。
LifoQueue 是一個 LIFO 隊列,類似堆棧,后添加的任務先被取出。
PriorityQueue 是一個優(yōu)先級隊列,隊列里面的任務按照優(yōu)先級排序,優(yōu)先級高的先被取出。
Queue 常用操作
類和異常
class queue.Queue(maxsize=0)class queue.LifoQueue(maxsize=0)class queue.PriorityQueue(maxsize=0)
如你所見,就是上面所說的三種不同類型的內(nèi)置隊列,其中 maxsize 是個整數(shù),用于設置可以放入隊列中的任務數(shù)的上限。當達到這個大小的時候,插入操作將阻塞至隊列中的任務被消費掉。如果 maxsize 小于等于零,則隊列尺寸為無限大。
exception queue.Empty# 對空的 Queue 對象調(diào)用非阻塞的 get() (or get_nowait()) 時,會引發(fā)該異常。exception queue.Full# 對滿的 Queue 對象調(diào)用非阻塞的 put() (or put_nowait()) 時,會引發(fā)該異常。
常用操作
1、添加任務
向隊列中添加任務,直接調(diào)用 put() 函數(shù)即可
>>> import queue>>> q = queue.Queue(maxsize=1)>>> q.put(100)
put() 函數(shù)完整的函數(shù)簽名如下 Queue.put(item, block=True, timeout=None),如你所見,該函數(shù)有兩個可選參數(shù)。
默認情況下,在隊列滿時,該函數(shù)會一直阻塞,直到隊列中有空余的位置可以添加任務為止。如果 timeout 是正數(shù),則最多阻塞 timeout 秒,如果這段時間內(nèi)還沒有空余的位置出來,則會引發(fā) Full 異常。
>>> import queue>>> q = queue.Queue(maxsize=1)>>> q.put(100)>>> q.put(100,True,2)Traceback (most recent call last): File “”, line 1, in File “E:Python37-32libqueue.py”, line 147, in put raise Fullqueue.Full # 創(chuàng)建一個容量為 1 的隊列,2 秒內(nèi)沒有位置添加任務則引發(fā) Full 異常>>> q.put(100) # 該方法會一直阻塞
當 block 為 false 時,timeout 參數(shù)將失效。同時如果隊列中沒有空余的位置可添加任務則會引發(fā) Full 異常,否則會直接把任務放入隊列并返回,不會阻塞。
>>> import queue>>> q = queue.Queue(maxsize=1)>>> q.put(100)>>> q.put(100,False,2)Traceback (most recent call last): File “”, line 1, in File “E:Python37-32libqueue.py”, line 136, in put raise Fullqueue.Full# 創(chuàng)建一個容量為 1 的隊列,在第二次放入任務時指定為非阻塞模式,則會立刻引發(fā) Full 異常
另外,還可以通過 Queue.put_nowait(item) 來添加任務,相當于 Queue.put(item, False),不再贅述。同樣,在隊列滿時,該操作會引發(fā) Full 異常。
2、獲取任務
從隊列中獲取任務,直接調(diào)用 get() 函數(shù)即可。
>>> import queue>>> q = queue.Queue()>>> q.put(100)>>> q.get()100
與 put() 函數(shù)一樣,get() 函數(shù)也有兩個可選參數(shù),完整簽名如下 Queue.get(block=True, timeout=None)。
默認情況下,當隊列空時調(diào)用該函數(shù)會一直阻塞,直到隊列中有任務可獲取為止。如果 timeout 是正數(shù),則最多阻塞 timeout 秒,如果這段時間內(nèi)還沒有任務可獲取,則會引發(fā) Empty 異常。
>>> import queue>>> q = queue.Queue()>>> q.put(100)>>> q.get()100>>> q.get(True,2)Traceback (most recent call last): File “”, line 1, in File “E:Python37-32libqueue.py”, line 178, in get raise Empty_queue.Empty # 2 秒鐘內(nèi)沒有任務可獲取則引發(fā) Empty 異常>>> q.get() # 該方法會一直阻塞
當 block 為 false 時,timeout 參數(shù)將失效。同時如果隊列中沒有任務可獲取則會立刻引發(fā) Empty 異常,否則會直接獲取一個任務并返回,不會阻塞。
>>> import queue>>> q = queue.Queue()>>> q.put(100)>>> q.get()100>>> q.get(False,2)Traceback (most recent call last): File “”, line 1, in File “E:Python37-32libqueue.py”, line 167, in get raise Empty_queue.Empty # 指定為非阻塞模式,隊列為空則立即引發(fā) Empty 異常
另外,還可以通過 Queue.get_nowait() 來獲取任務,相當于 Queue.get(False),不再贅述。同樣,在隊列為空時,該操作會引發(fā) Empty 異常。
其他常用方法
1、獲取隊列大小
Queue.qsize() 函數(shù)返回隊列的大小。注意這個大小不是精確的,qsize() > 0 不保證后續(xù)的 get() 不被阻塞,同樣 qsize() < maxsize 也不保證 put() 不被阻塞。
>>> import queue>>> q = queue.Queue()>>> q.put(100)>>> q.put(200)>>> q.qsize()2
2、判斷隊列是否空
如果隊列為空,返回 True ,否則返回 False 。如果 empty() 返回 True ,不保證后續(xù)調(diào)用的 put() 不被阻塞。類似的,如果 empty() 返回 False ,也不保證后續(xù)調(diào)用的 get() 不被阻塞。
3、判斷隊列是否滿
如果隊列是滿的返回 True ,否則返回 False 。如果 full() 返回 True 不保證后續(xù)調(diào)用的 get() 不被阻塞。類似的,如果 full() 返回 False 也不保證后續(xù)調(diào)用的 put() 不被阻塞。
>>> import queue>>> q = queue.Queue(maxsize=1)>>> q.empty()True>>> q.full()False>>> q.put(100)>>> q.empty()False>>> q.full()True
隊列對比
1、FIFO 隊列
queue.Queue() 是 FIFO 隊列,出隊順序跟入隊順序是一致的。
import queueq = queue.Queue()for index in range(10): q.put(index)while not q.empty(): print(q.get(), end=”, “)## 輸出結果如下0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
2、LIFO 隊列
queue.LifoQueue() 是 LIFO 隊列,出隊順序跟入隊順序是完全相反的,類似于棧。
import queueq = queue.LifoQueue() # 創(chuàng)建一個 LIFO 隊列for index in range(10): q.put(index)while not q.empty(): print(q.get(), end=”, “)## 輸出結果如下9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
3、優(yōu)先級隊列
優(yōu)先級隊列中的任務順序跟放入時的順序是無關的,而是按照任務的大小來排序,最小值先被取出。那任務比較大小的規(guī)則是怎么樣的呢。
3.1 如果是內(nèi)置類型,比如數(shù)值或者字符串,則按照自然順序來比較排序。
import queueq = queue.PriorityQueue()for index in range(10,0,-1): q.put(index)while not q.empty(): print(q.get(), end=”, “)## 輸出結果如下1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
3.2 如果是列表或者元祖,則先比較第一個元素,然后比較第二個,以此類推,直到比較出結果
import queueq = queue.PriorityQueue()q.put([“d”,”b”])q.put([“c”,”b”])while not q.empty(): print(q.get(), end=”, “)## 輸出結果如下[‘c’, ‘b’], [‘d’, ‘b’],
注意,因為列表的比較對規(guī)則是按照下標順序來比較的,所以在沒有比較出大小之前 ,隊列中所有列表對應下標位置的元素類型要一致。
好比 [2,1] 和 [“1″,”b”] 因為第一個位置的元素類型不一樣,所以是沒有辦法比較大小的,所以也就放入不了優(yōu)先級隊列。
然而對于 [2,1] 和 [1,”b”] 來說即使第二個元素的類型不一致也是可以放入優(yōu)先級隊列的,因為只需要比較第一個位置元素的大小就可以比較出結果了,就不需要比較第二個位置元素的大小了。
但是對于 [2,1] 和 1[2,”b”] 來說,則同樣不可以放入優(yōu)先級隊列,因為需要比較第二個位置的元素才可以比較出結果,然而第二個位置的元素類型是不一致的,無法比較大小。
綜上,也就是說,直到在比較出結果之前,對應下標位置的元素類型都是需要一致的。
3.3 如果是自定義類型,需要實現(xiàn)__lt__比較函數(shù)
下面我們自定義一個動物類型,希望按照年齡大小來做優(yōu)先級排序。年齡越小優(yōu)先級越高。
import queueq = queue.PriorityQueue()class Animal: def __init__(self, age, name): self.age = age self.name = name def __lt__(self, other): # 實現(xiàn) < 操作 return self.age < other.age # 如果將 則相當于逆序q.put(Animal(3,"cat"))q.put(Animal(2,"dog"))while not q.empty(): animal = q.get() print(animal.name, animal.age, end=", ")## 輸出結果如下dog 2, cat 3,
Queue 總結
本章節(jié)介紹了隊列以及其常用操作。因為隊列默認實現(xiàn)了鎖原語,因此在多線程編程中就不需要再考慮多線程安全問題了,對于程序員來說相當友好了。