11.python並發入門(part7 線程隊列)
一、為什麽要用隊列?
隊列是一種數據結構,數據結構是一種存放數據的容器,和列表,元祖,字典一樣,這些都屬於數據結構。
隊列可以做的事情,列表都可以做,但是為什麽我們還要去使用隊列呢?
這是因為在多線程的情況下,列表是一種不安全的數據結構。
為什麽不安全?可以看下面這個例子:
#開啟兩個線程,這兩個線程並發從列表中移除一個元素。
import threading
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函數的開頭部分開始重新執行......一直循環下去直到列表為空。
其實上面出現的這種情況,我們可以通過互斥鎖或者遞歸鎖的方式去解決。
如果不想加鎖,我們就可以使用隊列這種數據類型。
二、隊列的基本使用。
首先介紹下線程隊列的基本用法。
1、要使用線程隊列之前,首先需要導入一個名為Queue的模塊。
import Queue
2、初始化一個線程隊列的對象,這個隊列的長度可以無限,也可以有限,這個隊列的大小可以通過maxsize去指定。
q1 = Queue.Queue(maxsize=10)
3、將一個值放入隊列中。
q1.put(“a”)
調用隊列對象的put方法,實現的是在隊列的尾部插入一個數據,put方法有兩個參數,第一個是item,也就是要在隊列尾部插入的數據(這個是必填的),另外一個就是block參數,這個block參數如果不填,默認為1。
如果當一個隊列為空,並且block為1時,put方法就會使調用put方法的這個線程掛起,直到有空的數據單元,如果block設置為0,一旦隊列滿了,程序就會拋出一個full異常。
4、從隊列中取出一個值。
q1.get()
調用隊列的get方法,從隊列的頭部刪除並且返回一個元素,get方法也有一個可選參數,也叫block,默認為True。
當隊列為空,block為“True”時,調用get方法的那個線程會被掛起(保存狀態暫停),一直等到對了裏面有數據為止。
當隊列為空,block為“False”時,就會直接拋出Empty異常。
5、隊列的三種模式。
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()
本文出自 “reBiRTH” 博客,請務必保留此出處http://suhaozhi.blog.51cto.com/7272298/1925477
11.python並發入門(part7 線程隊列)