1. 程式人生 > >Python多執行緒學習

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()函式來產生新執行緒。如下例:

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()
       上面的例子定義了一個執行緒函式timer,它打印出10條時間記錄後退出,每次列印的間隔由interval引數決定。thread.start_new_thread(function, args[, kwargs])的第一個引數是執行緒函式(本例中的timer方法),第二個引數是傳遞給執行緒函式的引數,它必須是tuple型別,kwargs是可選引數。

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