現在把關於多線程的能想到的需要註意的點記錄一下: 關於threading模塊: 1、關於 傳參問題 如果調用的子線程函數需要傳參,要在參數後面加一個“,”否則會拋參數異常的錯誤。 如下: 2、關於join()阻塞 join()方法一旦被調用,這個線程就會被阻塞住,等其他線程執行完才執行自身。當我們在 ...
現在把關於多線程的能想到的需要註意的點記錄一下:
關於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 = m 10 11 def run(self): 12 print 'worker', self.m 13 time.sleep(1) 14 return 15 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, dish 12 while dish < 10: 13 if (random.random() > 0.5): 14 lock.acquire() 15 dish += 1 16 print('生產者增加了一個蘋果,現在有%d個蘋果' % (dish,)) 17 lock.release() 18 time.sleep(random.random() * 3) 19 20 21 def consumerFunction(): 22 '''如果投的篩子比0.5小,則從盤子中取一個蘋果''' 23 global lock, dish 24 while dish > 0: 25 if (random.random() < 0.5): 26 lock.acquire() 27 dish -= 1 28 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