簡析Python中的四種佇列

simpleapples發表於2018-05-21

佇列是一種只允許在一端進行插入操作,而在另一端進行刪除操作的線性表。

在Python文件中搜尋佇列(queue)會發現,Python標準庫中包含了四種佇列,分別是queue.Queue / asyncio.Queue / multiprocessing.Queue / collections.deque。

collections.deque

deque是雙端佇列(double-ended queue)的縮寫,由於兩端都能編輯,deque既可以用來實現棧(stack)也可以用來實現佇列(queue)。

deque支援豐富的操作方法,主要方法如圖:

簡析Python中的四種佇列

相比於list實現的佇列,deque實現擁有更低的時間和空間複雜度。list實現在出隊(pop)和插入(insert)時的空間複雜度大約為O(n),deque在出隊(pop)和入隊(append)時的時間複雜度是O(1)。

deque也支援in操作符,可以使用如下寫法:

q = collections.deque([1, 2, 3, 4])
print(5 in q)  # False
print(1 in q)  # True
複製程式碼

deque還封裝了順逆時針的旋轉的方法:rotate。

# 順時針
q = collections.deque([1, 2, 3, 4])
q.rotate(1)
print(q)  # [4, 1, 2, 3]
q.rotate(1)
print(q)  # [3, 4, 1, 2]

# 逆時針
q = collections.deque([1, 2, 3, 4])
q.rotate(-1)
print(q)  # [2, 3, 4, 1]
q.rotate(-1)
print(q)  # [3, 4, 1, 2]
複製程式碼

執行緒安全方面,通過檢視collections.deque中的append()、pop()等方法的原始碼可以知道,他們都是原子操作,所以是GIL保護下的執行緒安全方法。

static PyObject *
deque_append(dequeobject *deque, PyObject *item) { 
    Py_INCREF(item);
    if (deque_append_internal(deque, item, deque->maxlen) < 0) 
        return NULL;
    Py_RETURN_NONE;
}
複製程式碼

通過dis方法可以看到,append是原子操作(一行位元組碼)。

簡析Python中的四種佇列

綜上,collections.deque是一個可以方便實現佇列的資料結構,具有執行緒安全的特性,並且有很高的效能。

queue.Queue & asyncio.Queue

queue.Queue和asyncio.Queue都是支援多生產者、多消費者的佇列,基於collections.deque,他們都提供了Queue(FIFO佇列)、PriorityQueue(優先順序佇列)、LifoQueue(LIFO佇列),介面方面也相同。

區別在於queue.Queue適用於多執行緒的場景,asyncio.Queue適用於協程場景下的通訊,由於asyncio的加成,queue.Queue下的阻塞介面在asyncio.Queue中則是以返回協程物件的方式執行,具體差異如下表:

queue.Queue asyncio.Queue
介紹 同步佇列 asyncio佇列
執行緒安全
超時機制 通過timeout引數實現 通過asyncio.wait_for()方法實現
qsize() 預估的佇列長度(獲取qsize到下一個操作之間,queue有可能被其它的執行緒修改,導致qsize大小發生變化) 準確的佇列長度(由於是單執行緒,所以queue不會被其它執行緒修改)
put() / set() put(item, block=True, timeout=None),可以通過設定block是否為True來配置put和set方法是否為阻塞,並且可以為阻塞操作設定最大時長timeout,block為False時行為和put_nowait()方法一致。 put()方法會返回一個協程物件,所以沒有block引數和timeout引數,如果需要非阻塞方法,可以使用put_nowait(),如果需要對阻塞方法應用超時,可以使用coroutine asyncio.wait_for()。

multiprocessing.Queue

multiprocessing提供了三種佇列,分別是Queue、SimpleQueue、JoinableQueue。

簡析Python中的四種佇列

multiprocessing.Queue既是執行緒安全也是程式安全的,相當於queue.Queue的多程式克隆版。和threading.Queue很像,multiprocessing.Queue支援put和get操作,底層結構是multiprocessing.Pipe。

multiprocessing.Queue底層是基於Pipe構建的,但是資料傳遞時並不是直接寫入Pipe,而是寫入程式本地buffer,通過一個feeder執行緒寫入底層Pipe,這樣做是為了實現超時控制和非阻塞put/get,所以Queue提供了join_thread、cancel_join_thread、close函式來控制feeder的行為,close函式用來關閉feeder執行緒、join_thread用來join feeder執行緒,cancel_join_thread用來在控制在程式退出時,不自動join feeder執行緒,使用cancel_join_thread有可能導致部分資料沒有被feeder寫入Pipe而導致的資料丟失。

和threading.Queue不同的是,multiprocessing.Queue預設不支援join()和task_done操作,這兩個支援需要使用mp.JoinableQueue物件。

SimpleQueue是一個簡化的佇列,去掉了Queue中的buffer,沒有了使用Queue可能出現的問題,但是put和get方法都是阻塞的並且沒有超時控制。

總結

通過對比可以發現,上述四種結構都實現了佇列,但是用處卻各有偏重,collections.deque在資料結構層面實現了佇列,但是並沒有應用場景方面的支援,可以看做是一個基礎的資料結構。queue模組實現了面向多生產執行緒、多消費執行緒的佇列,asyncio.queue模組則實現了面向多生產協程、多消費協程的佇列,而multiprocessing.queue模組實現了面向多成產程式、多消費程式的佇列。

參考

docs.python.org/3/library/c…

docs.python.org/3/library/q…

docs.python.org/3/library/a…

docs.python.org/3/library/m…

bugs.python.org/issue15329

blog.ftofficer.com/2009/12/pyt…

cyrusin.github.io/2016/04/27/…

歡迎關注我的公眾號【Python私房菜】

簡析Python中的四種佇列

相關文章