11.python併發入門(part7 線程隊列)

1、爲何要用隊列?
python

隊列是一種數據結構,數據結構是一種存放數據的容器,和列表,元祖,字典同樣,這些都屬於數據結構。
安全

隊列能夠作的事情,列表均可以作,可是爲何咱們還要去使用隊列呢?數據結構

這是由於在多線程的狀況下,列表是一種不安全的數據結構。多線程

爲何不安全?能夠看下面這個例子:併發

#開啓兩個線程,這兩個線程併發從列表中移除一個元素。python2.7

import threadingide

import time函數

l1 = [1,2,3,4,5]線程

def pri():對象

    while l1:

        a = l1[-1]

        print a

        time.sleep(1)

        try:

            l1.remove(a)

        except Exception as e:

            print "%s-------%s" %(a,e)

t1 = threading.Thread(target=pri)

t1.start()

t2 = threading.Thread(target=pri)

t2.start()


輸出結果:

5

5

4

5-------list.remove(x): x not in list

4

34-------list.remove(x): x not in list

3

23-------list.remove(x): x not in list

2

1

2-------list.remove(x): x not in list

1

1-------list.remove(x): x not in list

關於上述的代碼分析:

主線程首先建立了兩個線程t1和t2 並啓動。

首先t1執行pri函數,首先獲取了列表中最後一個元素(5),而且print輸出,sleep1秒,此時切換到t2線程,t2線程執行pri函數,同時也獲取到了列表中的最後一個元素(5),print輸出,而後sleep1秒,此時切換回t1線程,t1線程執行l1.remove刪除剛剛獲取到的列表中的最後一個元素(5),成功刪除,沒有出現任何異常。

此時,列表中只剩下了[1,2,3,4]四個元素,t1線程從新再去執行pri函數,首先獲取列表中的最後一個元素(此時的最後一個元素是4),而且print輸出最後一個元素(4),print完畢以後sleep1秒,此時又會切換到t2線程,剛剛t2線程拿到的最後一個元素是5,剛剛t2線程拿到了元素5以後執行sleep 1後就阻塞了,如今t2線程開始執行sleep 1後面的代碼,l1.remove()如今t2線程要去刪除的是以前t2線程獲取到的最後一個元素5,可是元素5以前已經被t1線程從列表中刪掉了,因此如今元素5是不存在的,就會拋出一個異常,except下面的代碼塊就會運行,輸出一個「5-------list.remove(x): x not in list」 由於以前捕捉了異常,因此程序不會崩潰,而後繼續執行~

t2線程print一個錯誤信息「5-------list.remove(x): x not in list」後,回到pri函數的開頭,繼續從新執行,從新去得到l1列表中的最後一個元素(4),而後print輸出(4),再而後sleep1秒,切換到t1線程。

t1線程剛剛拿到的最後一個元素是(4)剛剛執行到了pri函數的sleep1,結束了sleep以後,開始執行list.remove移除列表中的最後一個元素,此時元素4從l1列表中被刪除,如今的l1列表中只剩下了[1,2,3]三個元素。

t1線程繼續回到pri函數開始的位置,獲取列表中的最後一個元素後(3)並print輸出,而後sleep,此時切換到t2線程,t2線程剛纔拿到的最後一個元素是4,顯然元素4已經被剛剛的t1線程給移除掉了,當t2去移除元素4時,又會出現異常,print輸出「4-------list.remove(x): x not in list」,而後t2線程繼續回到pri函數的開頭部分開始從新執行......一直循環下去直到列表爲空。



其實上面出現的這種狀況,咱們能夠經過互斥鎖或者遞歸鎖的方式去解決。

若是不想加鎖,咱們就可使用隊列這種數據類型。


2、隊列的基本使用。

首先介紹下線程隊列的基本用法。

一、要使用線程隊列以前,首先須要導入一個名爲Queue的模塊。

import Queue


二、初始化一個線程隊列的對象,這個隊列的長度能夠無限,也能夠有限,這個隊列的大小能夠經過maxsize去指定。

q1 = Queue.Queue(maxsize=10)


三、將一個值放入隊列中。

q1.put(「a」)

調用隊列對象的put方法,實現的是在隊列的尾部插入一個數據,put方法有兩個參數,第一個是item,也就是要在隊列尾部插入的數據(這個是必填的),另一個就是block參數,這個block參數若是不填,默認爲1。

若是當一個隊列爲空,而且block爲1時,put方法就會使調用put方法的這個線程掛起,直到有空的數據單元,若是block設置爲0,一旦隊列滿了,程序就會拋出一個full異常。


四、從隊列中取出一個值。

q1.get()

調用隊列的get方法,從隊列的頭部刪除而且返回一個元素,get方法也有一個可選參數,也叫block,默認爲True。

當隊列爲空,block爲「True」時,調用get方法的那個線程會被掛起(保存狀態暫停),一直等到對了裏面有數據爲止。

當隊列爲空,block爲「False」時,就會直接拋出Empty異常。


五、隊列的三種模式。

Queue.Queue :FIFO 先進先出。

Queue.LifoQueue:LIFO 後進先出(先進後出)

Queue.PriorityQueue:這是一種優先級隊列,給每一個隊列中的元素指定一個優先級,優先級越低的越先從隊列中出去。



6.關於隊列中可能會經常使用的其餘方法。

q1.qsize() 獲取當前隊列的大小。

q1.empty() 若是隊列爲空時,返回True,不然返回False

q1.full() 若是隊列滿了返回True,不然返回False。

q1.get([block[, timeout]]) 獲取隊列中的數據,timeout爲等待時間。

q1.get_nowait() 至關q1.get(block = False)

q1.put(item) 在隊列中添加數據,timeout爲等待時間。

q1.put_nowait(item) 至關q1.put(item, block=False)。

q1.task_done() 在完成一項工做以後,q1.task_done() 函數向任務已經完成的隊列發送一個信號。

q1.join() 實際上意味着等到隊列爲空,再執行別的操做.



在多線程中使用隊列這種數據類型去代替列表後:

#!/usr/local/bin/python2.7

# -*- coding:utf-8 -*-

import Queue

import threading

import time

q1 = Queue.PriorityQueue(maxsize=10)

for i in xrange(1,6):

    q1.put(i)

def pri():

    while not q1.empty():

        a = q1.get()

        print a

        time.sleep(1)

t1 = threading.Thread(target=pri)

t1.start()

t2 = threading.Thread(target=pri)

t2.start()

相關文章
相關標籤/搜索