再看python多線程------threading模塊


現在把關於多線程的能想到的需要注意的點記錄一下:

關於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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM