【python內功修煉010】:多線程之任務隊列Queue(詳解)


一、什麼是隊列?

Queue是python標準庫中的線程安全的隊列(FIFO)實現,提供了一個適用於多線程編程的先進先出的數據結構,即隊列,用來在生產者和消費者線程之間的信息傳遞。

隊列是一種特殊的線性表,特殊之處在於它只允許在表的前端(front)進行刪除操作,而在表的後端(rear)進行插入操作,和棧一樣,隊列是一種操作受限制的線性表。進行插入操作的端稱爲隊尾,進行刪除操作的端稱爲隊頭。隊列中沒有元素時,稱爲空隊列。

二、線程爲什麼要使用Queue(隊列)?

1、在進程中,各個進程內存空間是獨立的, 彼此之間不直接通訊,只能引用了消息隊列來進行通訊。

2、一個線程中,多個線程內存空間是共享的,直接就能通信,引用Queue的作用也是通信,不多此一舉?

3、由於每個線程都能修改共享資源, 如果不使用互斥鎖,那麼得到的結果有可能不可預期,因爲你的一句代碼,彙編可能是多行,這樣在執行其中一行時,或許另一個線程開始對變量進行操作,這樣會導致結果出現偏差。

4、 而python自帶一個互斥鎖,和三個條件變量,來保證了線程安全。

三、線程使用Queue的好處

1、保障線程安全:隊列內置有鎖線程安全的數據結構,不用關心數據怎麼放的,只要知道怎麼用就可以,怎麼插數據拿數據

2、解耦: 使程序直接實現鬆耦合,修改一個函數,不會有串聯關係。

3、提高處理效率:FIFO = 現進先出,LIFO = 後入先出。

四、 Python四種類型的Queue

方法 功能
Queue(maxsize=0) 創建一個先進先出隊列,maxsize是個整數,指明瞭隊列中能存放的數據個數的上限。一旦達到上限,插入會導致阻塞,直到隊列中的數據被消費掉;否則爲無限隊列
LifoQueue(maxsize=0) 創建一個後進先出的隊列,maxsize是個整數,指明瞭隊列中能存放的數據個數的上限。一旦達到上限,插入會導致阻塞,直到隊列中的數據被消費掉;否則爲無限隊列
Priority Queue(maxsize=0) 創建一個優先級隊列,級別越低,越優先。
deque() 雙邊隊列,也就是在序列的前後你都可以執行添加或刪除操作。

五、Queue對象的一些方法

方法 功能
Queue.qsize() 返回隊列大小
Queue.empty() 如果隊列爲空,返回True,反之False
Queue.full() 如果隊列滿了,返回True,反之False
Queue.put(item,block=True,timeout=None) 將item放如隊列
Queue.put_nowait(item) 和put(item, False)相同
Queue.get(block=True,timeout=None) 從隊列中取得元素
Queue.get_nowait() 和get(False)相同
Queue.task_done() 在完成一項工作後,向任務已經完成的隊列發送一個信號
Queue.join() 在隊列中所有元素執行完畢並調用上面的task_done()型號之前,保持阻塞
q.put方法用以插入數據到隊列中,put方法還有兩個可選參數:blocked和timeout。如果blocked爲True(默認值),並且timeout爲正值,該方法會阻塞timeout指定的時間,直到該隊列有剩餘的空間。如果超時,會拋出Queue.Full異常。如果blocked爲False,但該Queue已滿,會立即拋出Queue.Full異常。
q.get方法可以從隊列讀取並且刪除一個元素。同樣,get方法有兩個可選參數:blocked和timeout。如果blocked爲True(默認值),並且timeout爲正值,那麼在等待時間內沒有取到任何元素,會拋出Queue.Empty異常。如果blocked爲False,有兩種情況存在,如果Queue有一個值可用,則立即返回該值,否則,如果隊列爲空,則立即拋出Queue.Empty異常.
 
q.get_nowait():同q.get(False)
q.put_nowait():同q.put(False)
 
q.empty():調用此方法時q爲空則返回True,該結果不可靠,比如在返回True的過程中,如果隊列中又加入了項目。
q.full():調用此方法時q已滿則返回True,該結果不可靠,比如在返回True的過程中,如果隊列中的項目被取走。
q.qsize():返回隊列中目前項目的正確數量,結果也不可靠,理由同q.empty()和q.full()一樣

六、Queue隊列實例

6 .1 Queue實例

# 先進先出
# ==========================================
import queue

q=queue.Queue()
q.put('one')
q.put('two')
q.put('three')

print(q.get())
print(q.get())
print(q.get())
'''
結果(先進先出):
one
two
three
'''

6.2 LifoQueue 實例

# 先進後出概念
# ==========================================
import queue

q = queue.LifoQueue()
q.put('one')
q.put('two')
q.put('three')

print(q.get())
print(q.get())
print(q.get())
'''
結果(後進先出):
three
two
one
'''

6.3 PriorityQueue 實例

import queue

q = queue.PriorityQueue()
# put進入一個元組,元組的第一個元素是優先級(通常是數字,也可以是非數字之間的比較),數字越小優先級越高
q.put((20, 'a'))
q.put((10, 'b'))
q.put((30, 'c'))

print(q.get())
print(q.get())
print(q.get())
'''
結果(數字越小優先級越高,優先級高的優先出隊):
(10, 'b')
(20, 'a')
(30, 'c')
'''

6.4 collections. Deque 實例

# 雙邊隊列
# ===================================================================

import collections
dq = collections.deque(['a', 'b'])
dq.append('c')   # 增加數據到隊尾
dq.appendleft('d')  # 增加數據到隊首

print(dq)  # 輸出隊列所有數據

print(dq.pop())  # 移除隊尾,並返回
print(dq.popleft())  # 移除隊首,並返回

'''
結果
deque(['d', 'a', 'b', 'c'])
c
d

'''

七、消費者和生產者模型

接下來的實例,是有關生產者-消費者,使用Queue對象,以及隨機生產(消費)的商品數量。生產者和消費者獨立且併發執行線程。

參考我其他博文:【python內功修煉003】:併發編程之生產者與消費者模式

from threading import Thread
import queue
import time, random




# 生產者
def producer(name,food,q):
    for i in range(1, 5):
        time.sleep(2)  # 廚師每2秒做一個包子
        print('廚師【%s】:製作了 %s 個%s' % (name, i, food))
        res = '廚師【%s】製作的第【%s】%s' % (name, i, food)
        q.put(res)  # 將製作的包子裝在隊列裏面


# 消費者
def consumer(name, q):
    while True:
        res = q.get()  # 獲取隊列中的值
        time.sleep(random.randint(1, 2))  # 模擬吃包子時間
        print('消費者【%s】:吃 %s\n' % (name, res))
        q.task_done()  # 發送信號


if __name__ == "__main__":
    q = queue.Queue()  # 創建一個隊列

    # 生產者們:即廚師們
    p1 = Thread(target=producer, args=('金鞍少年', '包子', q))
    p2 = Thread(target=producer, args=('nancy', '牛肉麪', q))

    # 消費者們:即吃貨們
    c1 = Thread(target=consumer, args=('岳雲鵬', q,))
    c2 = Thread(target=consumer, args=('郭德綱', q,))
    # 設置守護進程
    c1.daemon = True
    c2.daemon = True

    # 開始
    p_l = [p1, p2, c1, c2]
    for p in p_l:
        p.start()

    p1.join()
    p2.join()
    print('主')

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