Python 標準庫提供了 thread 和 threading 兩個模塊來對多線程進行支持。其中, thread 模塊以低級、原始的方式來處理和控制線程,而 threading 模塊經過對 thread 進行二次封裝,提供了更方便的 api 來處理線程。 雖然使用 thread 沒有 threading 來的方便,但它更靈活。python
先看一段代碼:api
1 #coding=gbk 2 import thread, time, random 3 count = 0 4 def threadTest(): 5 global count 6 for i in xrange(10000): 7 count += 1 8 for i in range(10): 9 thread.start_new_thread(threadTest, ()) #若是對start_new_thread函數不是很瞭解,不要着急,立刻就會講解 10 time.sleep(3) 11 print count #count是多少呢?是10000 * 10 嗎?
函數將建立一個新的線程,並返回該線程的標識符(標識符爲整數)。參數 function 表示線程建立以後,當即執行的函數,參數 args 是該函數的參數,它是一個元組類型;第二個參數 kwargs 是可選的,它爲函數提供了命名參數字典。函數執行完畢以後,線程將自動退出。若是函數在執行過程當中遇到未處理的異常,該線程將退出,但不會影響其餘線程的執行。 多線程
一個簡單的例子:併發
#coding=gbk import thread, time def threadFunc(a = None, b = None, c = None, d = None): print time.strftime('%H:%M:%S', time.localtime()), a time.sleep(1) print time.strftime('%H:%M:%S', time.localtime()), b time.sleep(1) print time.strftime('%H:%M:%S', time.localtime()), c time.sleep(1) print time.strftime('%H:%M:%S', time.localtime()), d time.sleep(1) print time.strftime('%H:%M:%S', time.localtime()), 'over' thread.start_new_thread(threadFunc, (3, 4, 5, 6)) #建立線程,並執行threadFunc函數。 time.sleep(5)
結束當前線程。調用該函數會觸發 SystemExit 異常,若是沒有處理該異常,線程將結束。dom
返回當前線程的標識符,標識符是一個非零整數。ide
在主線程中觸發 KeyboardInterrupt 異常。子線程可使用該方法來中斷主線程。下面的例子演示了在子線程中調用 interrupt_main ,在主線程中捕獲異常 :函數
import thread, time thread.start_new_thread(lambda : (thread.interrupt_main(), ), ()) try: time.sleep(2) except KeyboardInterrupt, e: print 'error:', e print 'over'
下面介紹 thread 模塊中的瑣,瑣能夠保證在任什麼時候刻,最多隻有一個線程能夠訪問共享資源。ui
thread.LockType 是 thread 模塊中定義的瑣類型。它有以下方法:spa
獲取瑣。函數返回一個布爾值,若是獲取成功,返回 True ,不然返回 False 。參數 waitflag 的默認值是一個非零整數,表示若是瑣已經被其餘線程佔用,那麼當前線程將一直等待,只到其餘線程釋放,而後獲取訪瑣。若是將參數 waitflag 置爲 0 ,那麼當前線程會嘗試獲取瑣,無論瑣是否被其餘線程佔用,當前線程都不會等待。線程
釋放所佔用的瑣。
判斷瑣是否被佔用。
如今咱們回過頭來看文章開始處給出的那段代碼:代碼中定義了一個函數 threadTest ,它將全局變量逐一的增長 10000 ,而後在主線程中開啓了 10 個子線程來調用 threadTest 函數。但結果並非預料中的 10000 * 10 ,緣由主要是對 count 的併發操做引來的。全局變量 count 是共享資源,對它的操做應該串行的進行。下面對那段代碼進行修改,在對 count 操做的時候,進行加瑣處理。看看程序運行的結果是否和預期一致。修改後的代碼:
#coding=gbk import thread, time, random count = 0 lock = thread.allocate_lock() #建立一個瑣對象 def threadTest(): global count, lock lock.acquire() #獲取瑣 for i in xrange(10000): count += 1 lock.release() #釋放瑣 for i in xrange(10): thread.start_new_thread(threadTest, ()) time.sleep(3) print count
threading經過對thread模塊進行二次封裝,提供了更方便的API來操做線程。
Thread 是threading模塊中最重要的類之一,可使用它來建立線程。有兩種方式來建立線程:一種是經過繼承Thread類,重寫它的run方法;另外一種是建立一個threading.Thread對象,在它的初始化函數(__init__)中將可調用對象做爲參數傳入。下面分別舉例說明。先來看看經過繼承threading.Thread類來建立線程的例子:
1 #coding=gbk 2 import threading, time, random 3 count = 0 4 class Counter(threading.Thread): 5 def __init__(self, lock, threadName): 6 '''@summary: 初始化對象。 7 8 @param lock: 瑣對象。 9 @param threadName: 線程名稱。 10 ''' 11 super(Counter, self).__init__(name = threadName) #注意:必定要顯式的調用父類的初始 12 化函數。 13 self.lock = lock 14 15 def run(self): 16 '''@summary: 重寫父類run方法,在線程啓動後執行該方法內的代碼。 17 ''' 18 global count 19 self.lock.acquire() 20 for i in xrange(10000): 21 count = count + 1 22 self.lock.release() 23 lock = threading.Lock() 24 for i in range(5): 25 Counter(lock, "thread-" + str(i)).start() 26 time.sleep(2) #確保線程都執行完畢 27 print count
在代碼中,咱們建立了一個Counter類,它繼承了threading.Thread。初始化函數接收兩個參數,一個是瑣對象,另外一個是線程的名稱。在Counter中,重寫了從父類繼承的run方法,run方法將一個全局變量逐一的增長10000。在接下來的代碼中,建立了五個Counter對象,分別調用其start方法。最後打印結果。這裏要說明一下run方法 和start方法: 它們都是從Thread繼承而來的,run()方法將在線程開啓後執行,能夠把相關的邏輯寫到run方法中(一般把run方法稱爲活動[Activity]。);start()方法用於啓動線程。
再看看另一種建立線程的方法:
1 import threading, time, random 2 count = 0 3 lock = threading.Lock() 4 def doAdd(): 5 '''@summary: 將全局變量count 逐一的增長10000。 6 ''' 7 global count, lock 8 lock.acquire() 9 for i in xrange(10000): 10 count = count + 1 11 lock.release() 12 for i in range(5): 13 threading.Thread(target = doAdd, args = (), name = 'thread-' + str(i)).start() 14 time.sleep(2) #確保線程都執行完畢 15 print count
在這段代碼中,咱們定義了方法doAdd,它將全局變量count 逐一的增長10000。而後建立了5個Thread對象,把函數對象doAdd 做爲參數傳給它的初始化函數,再調用Thread對象的start方法,線程啓動後將執行doAdd函數。這裏有必要介紹一下threading.Thread類的初始化函數原型:
def __init__(self, group=None, target=None, name=None, args=(), kwargs={})
Thread類還定義瞭如下經常使用方法與屬性:
用於獲取和設置線程的名稱。
獲取線程的標識符。線程標識符是一個非零整數,只有在調用了start()方法以後該屬性纔有效,不然它只返回None。
判斷線程是不是激活的(alive)。從調用start()方法啓動線程,到run()方法執行完畢或遇到未處理異常而中斷 這段時間內,線程是激活的。
調用Thread.join將會使主調線程堵塞,直到被調用線程運行結束或超時。參數timeout是一個數值類型,表示超時時間,若是未提供該參數,那麼主調線程將一直堵塞到被調線程結束。下面舉個例子說明join()的使用:
1 import threading, time 2 def doWaiting(): 3 print 'start waiting:', time.strftime('%H:%M:%S') 4 time.sleep(3) 5 print 'stop waiting', time.strftime('%H:%M:%S') 6 thread1 = threading.Thread(target = doWaiting) 7 thread1.start() 8 time.sleep(1) #確保線程thread1已經啓動 9 print 'start join' 10 thread1.join() #將一直堵塞,直到thread1運行結束。 11 print 'end join'
在threading模塊中,定義兩種類型的瑣:threading.Lock和threading.RLock。它們之間有一點細微的區別,經過比較下面兩段代碼來講明:
1 import threading 2 lock = threading.Lock() #Lock對象 3 lock.acquire() 4 lock.acquire() #產生了死瑣。 5 lock.release() 6 lock.release()
1 import threading 2 rLock = threading.RLock() #RLock對象 3 rLock.acquire() 4 rLock.acquire() #在同一線程內,程序不會堵塞。 5 rLock.release() 6 rLock.release()
這兩種瑣的主要區別是:RLock容許在同一線程中被屢次acquire。而Lock卻不容許這種狀況。注意:若是使用RLock,那麼acquire和release必須成對出現,即調用了n次acquire,必須調用n次的release才能真正釋放所佔用的瑣。
能夠把Condiftion理解爲一把高級的瑣,它提供了比Lock, RLock更高級的功能,容許咱們可以控制複雜的線程同步問題。threadiong.Condition在內部維護一個瑣對象(默認是RLock),能夠在建立Condigtion對象的時候把瑣對象做爲參數傳入。Condition也提供了acquire, release方法,其含義與瑣的acquire, release方法一致,其實它只是簡單的調用內部瑣對象的對應的方法而已。Condition還提供了以下方法(特別要注意:這些方法只有在佔用瑣(acquire)以後才能調用,不然將會報RuntimeError異常。):
wait方法釋放內部所佔用的瑣,同時線程被掛起,直至接收到通知被喚醒或超時(若是提供了timeout參數的話)。當線程被喚醒並從新佔有瑣的時候,程序纔會繼續執行下去。
喚醒一個掛起的線程(若是存在掛起的線程)。注意:notify()方法不會釋放所佔用的瑣。
喚醒全部掛起的線程(若是存在掛起的線程)。注意:這些方法不會釋放所佔用的瑣。
Event實現與Condition相似的功能,不過比Condition簡單一點。它經過維護內部的標識符來實現線程間的同步問題。(threading.Event和.NET中的System.Threading.ManualResetEvent類實現一樣的功能。)
堵塞線程,直到Event對象內部標識位被設爲True或超時(若是提供了參數timeout)。
將標識位設爲Ture
將標識伴設爲False。
判斷標識位是否爲Ture。
threading.Timer是threading.Thread的子類,能夠在指定時間間隔後執行某個操做。下面是Python手冊上提供的一個例子:
1 def hello(): 2 print "hello, world" 3 t = Timer(3, hello) 4 t.start() # 3秒鐘以後執行hello函數。
threading模塊中還有一些經常使用的方法沒有介紹:
獲取當前活動的(alive)線程的個數。
獲取當前的線程對象(Thread object)。
獲取當前全部活動線程的列表。
設置一個跟蹤函數,用於在run()執行以前被調用。
設置一個跟蹤函數,用於在run()執行完畢以後調用。