再看python多線程------threading模組,pythonthreading模組
現在把關於多線程的能想到的需要注意的點記錄一下:
關於threading模組:
1、關於 傳參問題
如果調用的子線程函數需要傳參,要在參數後面加一個“,”否則會拋參數異常的錯誤。
如下:
1 for i in xrange(5):2 threads.append(threading.Thread(target=worker,args=(i,)))
2、關於join()阻塞
join()方法一旦被調用,這個線程就會被阻塞住,等其他線程執行完才執行自身。當我們在主線程A中,建立了n個子線程,這裡需要注意,根據需求是應該去阻塞父線程還是子線程,還有就是t.join()放的位置,比較下面例子:
---->子線程和父線程都不阻塞
1 # -*- coding:utf-8 -*- 2 import Queue,time,threading 3 start = time.clock() 4 5 def worker(m): 6 print 'worker',m 7 time.sleep(1) 8 return 9 10 if __name__ == "__main__":11 threads = []12 for i in xrange(5):13 threads.append(threading.Thread(target=worker,args=(i,)))14 for t in threads:15 t.start()16 #t.join() #阻塞子線程17 18 #t.join() #阻塞父線程19 20 end = time.clock()21 print "finished: %.3fs" %(end-start)
得到輸入:
worker 0
worker 1
worker 2
worker 3
worker 4
finished: 0.001s
這裡,其實父線程已經結束,因為已經列印出了finished:0.001s,但是子線程並沒有執行完,sleep 1秒之後,才出現“Process finished with exit code 0”的程式結束標誌。
----->同樣代碼,當阻塞子線程時,輸出如下:
worker 0
worker 1
worker 2
worker 3
worker 4
finished: 5.004s
這裡,由於5個子線程分別剛被t.start()之後,立即把自身阻塞了,所以它們會按序執行,同樣,程式sleep了5秒,一定要注意,你這樣做,程式的效率並沒有提升,仍然需要5秒的時間。所以這樣是有問題的,問題出在t.join()代碼放的位置,應該再 for t in threads: t.join(),使得這些線程同時start,然後同時join()。
-------> 同樣代碼,當阻塞父線程時,輸出如下:
worker 0
worker 1
worker 2
worker 3
worker 4
finished: 1.003s
這裡,阻塞父線程,父線程會等待子線程結束,才會繼續運行列印finished,程式的效率也得到了提升。這相當於上面提到的,先把所有的子線程start了,再join掉。
3、關於setDaemon()方法
setDaemon()方法是設定在子線程中的,當我們在父線程A中建立了n個子線程之後,給我們喜歡的子線程設定setDaemon(True)後,當它們的父線程運行結束之後,不管這些子線程運行結束還是沒結束,它會直接結束程式。這裡,還有一個需要注意的,setDaemon()方法必須設定在start()方法之前,否則會拋RuntimeError異常。
還用上面的例子:
1 # -*- coding:utf-8 -*- 2 import Queue,time,threading 3 start = time.clock() 4 5 def worker(m): 6 print 'worker',m 7 time.sleep(1) 8 return 9 10 if __name__ == "__main__":11 threads = []12 for i in xrange(5):13 threads.append(threading.Thread(target=worker,args=(i,)))14 for t in threads:15 t.setDaemon(True)16 t.start()17 #t.join() #阻塞子線程18 19 #t.join() #阻塞父線程20 21 end = time.clock()22 print "finished: %.3fs" %(end-start)
這裡,沒有阻塞父線程,得到的輸出如下:
worker 0
worker 1
worker 2
worker 3
worker 4
finished: 0.001s
說明,主線程一旦結束,會直接把子線程的記憶體回收,結束整個進程的運行。子進程的sleep 1沒有執行就退出了。對於某些輔助子線程的應用情境,這個應該會有用。
4、建立子線程的兩種方式
第一種是上面提到的,建立子線程要執行的函數(worker),然後把這個函數傳遞進threading.Thread的對象中,讓它來執行;
第二種是直接從threading.Thread類繼承,建立一個新的類,通過重寫這個新的類裡面的run()方法,實現子線程要執行的內容,例如:
1 # -*- coding:utf-8 -*- 2 __author__ = 'webber' 3 import threading,time 4 5 class Mythread(threading.Thread): 6 7 def __init__(self,m): 8 threading.Thread.__init__(self) 9 self.m = m10 11 def run(self):12 print 'worker', self.m13 time.sleep(1)14 return15 16 if __name__ == "__main__":17 start = time.clock()18 19 threads = []20 for i in xrange(5):21 threads.append(Mythread(i))22 for t in threads:23 t.start()24 25 t.join()26 end = time.clock()27 print "finished: %.3fs" % (end - start)
輸出和上面的主線程阻塞的結果一樣
這裡要注意一下黃色部分,調用的時候的傳參方式。
5、關於鎖----> Lock、RLock、Condition方法
之前有提到,由於python理論上是無法實現真正意義上的多線程的,即使你有多個CPU,python的多線程也只能利用一個,那麼為了防止在多線程中對共用資料空間的資料修改時發生的尷尬,threading模組繼承了thread模組的Lock方法,這是最簡單的鎖,實現也比較簡單,只需要在子線程中修改資料前後分別加上鎖和釋放鎖即可。
就是以下三句話:
a、主函數中建立一個鎖的對象: 例如: lock = threading.Lock() #返回一個新的Lock對象,建立一把鎖。
b、在子線程需要對資料進行修改之前,lock.acquire() #擷取這把鎖
c、在子線程對資料進行修改之後, lock.acquire() #釋放這把鎖
下面有個代碼應用小例子:
1 # -*- coding:utf-8 -*- 2 __author__ = 'webber' 3 import threading, time, random 4 5 dish = 0 6 lock = threading.Lock() 7 8 9 def producerFunction():10 '''如果投的篩子比0.5大,則向盤子中增加一個蘋果'''11 global lock, dish12 while dish < 10:13 if (random.random() > 0.5):14 lock.acquire()15 dish += 116 print('生產者增加了一個蘋果,現在有%d個蘋果' % (dish,))17 lock.release()18 time.sleep(random.random() * 3)19 20 21 def consumerFunction():22 '''如果投的篩子比0.5小,則從盤子中取一個蘋果'''23 global lock, dish24 while dish > 0:25 if (random.random() < 0.5):26 lock.acquire()27 dish -= 128 print('消費者拿走一個蘋果現,現在有%d個蘋果' % (dish,))29 lock.release()30 time.sleep(random.random() * 3)31 32 33 def begin():34 ident1 = threading.Thread(target=producerFunction())35 ident2 = threading.Thread(target=consumerFunction())36 ident1.start()37 ident2.start()38 39 40 if __name__ == '__main__':41 begin()View Code
其次,threading模組提出了一個更進階的鎖RLock,它的出現是為瞭解決Lock可能會出現的死結問題,即:當由於疏忽時,可能會出現一個子線程內同一把鎖對象連續acquire()兩次,那麼由於第一次的acquire沒有release,那麼第二次的acquire請求會把該子線程掛起,導致lock對象永遠不會release,造成死結。而RLock對象允許一個線程多次對其進行acquire操作,在其內部通過counter變數維護著線程acquire的次數,而每一次的acquire操作必須有一個release操作與之對應,在所有的release操作完成之後,別的線程才能申請該RLock對象。使用上暫時我就把它當成Lock方法試了試,通過。~~~
最後,threading模組提供了更進階的封裝,算是一種進階的多線程間同步方式,包括threading.Event和threading.Condition,其中,threading.Event為簡單的同步方式,一個進程標記為event,其他的進程就需要等待,用到下面幾種方法:
| Event.wait([timeout]) |
阻塞線程,直到Event對象內部標識位被設定為True或逾時(如果提供了參數timeout) |
| Event.set() |
將標識號設為True |
| Event.clear() |
設為標識符False |
threading.Condition 可以把Condition理解為更進階的鎖,它提供了比RLock更進階的功能,允許我們能夠控制複雜的線程同步問題,它在內部維護了一個鎖對象(預設為RLock),可以在建立Condition對象的時候把鎖對象作為參數傳入。Condition也提供了acquire和release方法,它的特色在於內部的wait和notify機制,具體可看threading模組,下面的方法只有在對象擷取到鎖之後才能調用,否則,將會拋RuntimeError異常。
| Condition.wait([timeout]): |
wait方法釋放內部所佔用的瑣,同時線程被掛起,直至接收到通知被喚醒或逾時(如果提供了timeout參數的話)。當線程被喚醒並重新佔有瑣的時候,程式才會繼續執行下去。 |
| Condition.notify() |
喚醒一個掛起的線程(如果存在掛起的線程)。注意:notify()方法不會釋放所佔用的瑣。 |
| Condition.notify_all() |
喚醒所有掛起的線程(如果存在掛起的線程)。注意:這些方法不會釋放所佔用的瑣。 |
參考:http://orangeholic.iteye.com/blog/1720421
6、其他方法
由於threading是繼承的thread模組的,所以還有些公用屬性方法,比如:
t.getName():擷取子線程的名稱,預設為:Tread-n (n為線程式列號)
t.setName():設定子線程的名稱
t.ident:擷取線程標識符,要在t.start()之後調用才有效,否則返回None
t.is_alive():判斷子線程是否啟用,返回True或False
關於Semaphore、event、Condition的具體執行個體,沒再去嘗試,以後遇到再試,可參考這篇部落格:
http://www.jb51.net/article/57672.htm