1. 程式人生 > >11.python並發入門(part7 線程隊列)

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

python 線程 隊列 queue

一、為什麽要用隊列?

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

隊列可以做的事情,列表都可以做,但是為什麽我們還要去使用隊列呢?

這是因為在多線程的情況下,列表是一種不安全的數據結構。

為什麽不安全?可以看下面這個例子:

#開啟兩個線程,這兩個線程並發從列表中移除一個元素。

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 線程隊列)