Python多執行緒學習
Python程式碼程式碼的執行由python虛擬機器(也叫直譯器主迴圈)來控制。Python在設計之初就考慮到要在主迴圈中,同時只有一個執行緒在執行,就像單CPU的系統中執行多個程序那樣,記憶體中可以存放多個程式,但任意時候,只有一個程式在CPU中執行。同樣,雖然python直譯器可以“執行”多個執行緒,但在任意時刻,只有一個執行緒在直譯器中執行。(據說新版本有考慮)
python支援多執行緒主要是通過thread和threading這兩個模組來實現的。thread是比較底層的模 塊,threading是對thread做了一些包裝的,可以更加方便的被使用。threading模組裡面主要是對一些執行緒的操作物件化了,建立了叫Thread的class。
其中Thread類是你主要的執行物件,它有很多函式,用它你可以用多種方法來建立執行緒,常用的為以下三種。 1.建立一個Thread的例項,傳給它一個函式(thread和threading兩種模組的實現);2. 建立一個Thread例項,傳給它一個可呼叫的類物件; 3.從Thread派生出一個子類,建立一個這個子類的例項。
1、 函式式:
呼叫thread模組中的start_new_thread()函式來產生新執行緒。如下例:
上面的例子定義了一個執行緒函式timer,它打印出10條時間記錄後退出,每次列印的間隔由interval引數決定。thread.start_new_thread(function, args[, kwargs])的第一個引數是執行緒函式(本例中的timer方法),第二個引數是傳遞給執行緒函式的引數,它必須是tuple型別,kwargs是可選引數。import time import thread def timer(no, interval): cnt = 0 while cnt<10: print 'Thread:(%d) Time:%s/n'%(no, time.ctime()) time.sleep(interval) cnt+=1 thread.exit_thread() def test(): #Use thread.start_new_thread() to create 2 new threads thread.start_new_thread(timer, (1,1)) thread.start_new_thread(timer, (2,2)) if __name__=='__main__': test()
PS:tuple是python中一個相對簡單的型別,它的特點是:有順序的、不可變的。因此,很顯然地tuple有像list和string一樣的 indexing和slicing(分片)的功能,可以通過標號對成員進行訪問。同時由於tuple是不可變的,因此試圖改變tuple成員的是非法的。
執行緒的結束可以等待執行緒自然結束,也可以線上程函式中呼叫thread.exit()或thread.exit_thread()方法。
建立Thread例項,傳遞一個函式給它:
import threading
from time import sleep,ctime
loops=[4,2]
def loop(nloop,nsec):
print 'start loop',nloop,'at:',ctime()
sleep(nsec)
print 'loop',nloop,'done at:',ctime()
def main():
print 'starting at:',ctime()
threads=[]
nloops=range(len(loops))
for i in nloops:
t=threading.Thread(target=loop,args=(i,loops[i]))
threads.append(t)
for i in nloops:
threads[i].start()
for i in nloops:
threads[i].join()
print 'all done at:',ctime()
if __name__=='__main__':
main()
2、 傳遞類物件:建立一個例項,傳遞一個可呼叫的類的物件 import threading
from time import sleep,ctime
loops=[4,2]
class ThreadFunc(object):
def __init__(self,func,args,name=''):
self.name=name
self.func=func
self.args=args
def __call__(self):
self.res=self.func(*self.args)
def loop(nloop,nsec):
print 'start loop',nloop,'at:',ctime()
sleep(nsec)
print 'loop',nloop,'done at:',ctime()
def main():
print 'starting at:',ctime()
threads=[]
nloops=range(len(loops))
for i in nloops:
t=threading.Thread(target=ThreadFunc(loop,(i,loops[i]),loop.__name__))
threads.append(t)
for i in nloops:
threads[i].start()
for i in nloops:
threads[i].join()
print 'all done at:',ctime()
if __name__=='__main__':
main()
3、 建立threading.Thread的子類來包裝一個執行緒物件,如下例:import threading
import time
class timer(threading.Thread): #The timer class is derived from the class threading.Thread
def __init__(self, num, interval):
threading.Thread.__init__(self)
self.thread_num = num
self.interval = interval
self.thread_stop = False
def run(self): #Overwrite run() method, put what you want the thread do here
while not self.thread_stop:
print 'Thread Object(%d), Time:%s/n' %(self.thread_num, time.ctime())
time.sleep(self.interval)
def stop(self):
self.thread_stop = True
def test():
thread1 = timer(1, 1)
thread2 = timer(2, 2)
thread1.start()
thread2.start()
time.sleep(10)
thread1.stop()
thread2.stop()
return
if __name__ == '__main__':
test()
threading.Thread類的使用:
1,在自己的執行緒類的__init__裡呼叫threading.Thread.__init__(self, name = threadname) Threadname為執行緒的名字
2,run(),通常需要重寫,編寫程式碼實現做需要的功能。
3,getName(),獲得執行緒物件名稱
4,setName(),設定執行緒物件名稱
5,start(),啟動執行緒
6,jion([timeout]),等待另一執行緒結束後再執行。
7,setDaemon(bool),設定子執行緒是否隨主執行緒一起結束,必須在start()之前呼叫。預設為False。
8,isDaemon(),判斷執行緒是否隨主執行緒一起結束。
9,isAlive(),檢查執行緒是否在執行中。
假設兩個執行緒物件t1和t2都要對num=0進行增1運算,t1和t2都各對num修改10次,num的最終的結果應該為20。但是由於是多執行緒訪問,有可能出現下面情況:在num=0時,t1取得num=0。系統此時把t1排程為”sleeping”狀態,把t2轉換為”running”狀態,t2頁獲得num=0。然後t2對得到的值進行加1並賦給num,使得num=1。然後系統又把t2排程為”sleeping”,把t1轉為”running”。執行緒t1又把它之前得到的0加1後賦值給num。這樣,明明t1和t2都完成了1次加1工作,但結果仍然是num=1。
上面的case描述了多執行緒情況下最常見的問題之一:資料共享。當多個執行緒都要去修改某一個共享資料的時候,我們需要對資料訪問進行同步。
1、 簡單的同步
最簡單的同步機制就是“鎖”。鎖物件由threading.RLock類建立。執行緒可以使用鎖的acquire()方法獲得鎖,這樣鎖就進入“locked”狀態。每次只有一個執行緒可以獲得鎖。如果當另一個執行緒試圖獲得這個鎖的時候,就會被系統變為“blocked”狀態,直到那個擁有鎖的執行緒呼叫鎖的release()方法來釋放鎖,這樣鎖就會進入“unlocked”狀態。“blocked”狀態的執行緒就會收到一個通知,並有權利獲得鎖。如果多個執行緒處於“blocked”狀態,所有執行緒都會先解除“blocked”狀態,然後系統選擇一個執行緒來獲得鎖,其他的執行緒繼續沉默(“blocked”)。
Python中的thread模組和Lock物件是Python提供的低階執行緒控制工具,使用起來非常簡單。如下例所示:
import thread
import time
mylock = thread.allocate_lock() #Allocate a lock
num=0 #Shared resource
def add_num(name):
global num
while True:
mylock.acquire() #Get the lock
# Do something to the shared resource
print 'Thread %s locked! num=%s'%(name,str(num))
if num >= 5:
print 'Thread %s released! num=%s'%(name,str(num))
mylock.release()
thread.exit_thread()
num+=1
print 'Thread %s released! num=%s'%(name,str(num))
mylock.release() #Release the lock.
def test():
thread.start_new_thread(add_num, ('A',))
thread.start_new_thread(add_num, ('B',))
if __name__== '__main__':
test()
Python 在thread的基礎上還提供了一個高階的執行緒控制庫,就是之前提到過的threading。Python的threading module是在建立在thread module基礎之上的一個module,在threading module中,暴露了許多thread module中的屬性。在thread module中,python提供了使用者級的執行緒同步工具“Lock”物件。而在threading module中,python又提供了Lock物件的變種:
RLock物件。RLock物件內部維護著一個Lock物件,它是一種可重入的物件。對於Lock物件而言,如果一個執行緒連續兩次進行acquire操作,那麼由於第一次acquire之後沒有release,第二次acquire將掛起執行緒。這會導致Lock物件永遠不會release,使得執行緒死鎖。RLock物件允許一個執行緒多次對其進行acquire操作,因為在其內部通過一個counter變數維護著執行緒acquire的次數。而且每一次的acquire操作必須有一個release操作與之對應,在所有的release操作完成之後,別的執行緒才能申請該RLock物件。
下面來看看如何使用threading的RLock物件實現同步。
import threading
mylock = threading.RLock()
num=0
class myThread(threading.Thread):
def __init__(self, name):
threading.Thread.__init__(self)
self.t_name = name
def run(self):
global num
while True:
mylock.acquire()
print '/nThread(%s) locked, Number: %d'%(self.t_name, num)
if num>=4:
mylock.release()
print '/nThread(%s) released, Number: %d'%(self.t_name, num)
break
num+=1
print '/nThread(%s) released, Number: %d'%(self.t_name, num)
mylock.release()
def test():
thread1 = myThread('A')
thread2 = myThread('B')
thread1.start()
thread2.start()
if __name__== '__main__':
test()
我們把修改共享資料的程式碼成為“臨界區”。必須將所有“臨界區”都封閉在同一個鎖物件的acquire和release之間。
2、 條件同步
鎖只能提供最基本的同步。假如只在發生某些事件時才訪問一個“臨界區”,這時需要使用條件變數Condition。
Condition物件是對Lock物件的包裝,在建立Condition物件時,其建構函式需要一個Lock物件作為引數,如果沒有這個Lock物件引數,Condition將在內部自行建立一個Rlock物件。在Condition物件上,當然也可以呼叫acquire和release操作,因為內部的Lock物件本身就支援這些操作。但是Condition的價值在於其提供的wait和notify的語義。
條件變數是如何工作的呢?首先一個執行緒成功獲得一個條件變數後,呼叫此條件變數的wait()方法會導致這個執行緒釋放這個鎖,並進入“blocked”狀態,直到另一個執行緒呼叫同一個條件變數的notify()方法來喚醒那個進入“blocked”狀態的執行緒。如果呼叫這個條件變數的notifyAll()方法的話就會喚醒所有的在等待的執行緒。
如果程式或者執行緒永遠處於“blocked”狀態的話,就會發生死鎖。所以如果使用了鎖、條件變數等同步機制的話,一定要注意仔細檢查,防止死鎖情況的發生。對於可能產生異常的臨界區要使用異常處理機制中的finally子句來保證釋放鎖。等待一個條件變數的執行緒必須用notify()方法顯式的喚醒,否則就永遠沉默。保證每一個wait()方法呼叫都有一個相對應的notify()呼叫,當然也可以呼叫notifyAll()方法以防萬一。
參考地址:
http://www.python.org/doc/2.5.2/lib/module-threading.html