雖然python
中由於GIL
的機制致使多執行緒不能利用機器多核的特性,但是多執行緒對於我們理解併發模型以及底層操作非常有用。
執行緒的有兩種使用方法,一種是在函式使用,一種是放在類中使用。
1,在函式中使用多執行緒
語法如下:
1 |
thread.start_new_thread(function, args[, kwargs] ) |
引數說明:
1 2 3 |
function - 執行緒函式。 args - 傳遞給執行緒函式的引數,必須是個tuple型別。 kwargs - 可選引數。 |
下面是一個例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def run(num): print 'hi , i am a thread.', num def main(): threads = [] for i in range(5): t = threading.Thread(target=run, args=(i,)) threads.append(t) t.start() for t in threads: t.join() if __name__ == '__main__': print 'start -->' main() print 'go here -->' |
執行結果:
1 2 3 4 5 6 7 |
start --> hi , i am a thread. 0 hi , i am a thread. 1 hi , i am a thread. 2 hi , i am a thread. 3 hi , i am a thread. 4 go here --> |
2,在類中多使用執行緒
下面是在類中使用執行緒的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class MyThread(threading.Thread): def __init__(self,num): self.num = num super(MyThread, self).__init__() def run(self): print 'i am a thread,',self.num time.sleep(1) def main(): threads = [] for i in range(5): t = MyThread(i) threads.append(t) t.start() for t in threads: t.join() if __name__ == '__main__': print 'start -->' main() print 'go here --> |
- run(),需要重寫,編寫程式碼實現所需要的功能。
- getName(),獲得執行緒物件名稱
- setName(),設定執行緒物件名稱
- start(),啟動執行緒
- join([timeout]),等待另一執行緒結束後再執行。
- setDaemon(bool),設定子執行緒是否隨主執行緒一起結束,必須在
start()
之前呼叫,預設為False
。 - isDaemon(),判斷執行緒是否隨主執行緒一起結束。
- isAlive(),檢查執行緒是否在執行中。
join
方法的作用是阻塞主程式(無法執行join
以後的語句),主執行緒等待這個執行緒結束後,才可以執行下一條指令。多執行緒多join
的情況下,依次執行各執行緒的join
方法,前頭一個結束了才能執行後面一個。無引數,則等待到該執行緒結束,才開始執行下一個執行緒的join
。設定引數後,則等待該執行緒這麼長時間就不管它了(而該執行緒並沒有結束)。不管的意思就是可以執行後面的主程式了。
3,執行緒同步與互斥鎖
執行緒之所以比程式輕量,其中一個原因就是他們共享記憶體。也就是各個執行緒可以平等的訪問記憶體的資料,如果在短時間“同時並行”讀取修改記憶體的資料,很可能造成資料不同步。例如下面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
var = 0 class IncreThread(Thread): def run(self): global var print 'before,var is ',var var += 1 print 'after,var is ',var def use_incre_thread(): threads = [] for i in range(50): t = IncreThread() threads.append(t) t.start() for t in threads: t.join() print 'After 10 times,var is ',var if __name__ == '__main__': use_incre_thread() |
有一個全域性變數var
,五十個執行緒,每個執行緒對var
變數進行加 1 運算,但是當你多執行幾次後,發現並不是每次的執行結果都是 50,為什麼呢?
在
var
是 10 的時候,執行緒t1
讀取了var
,這個時刻cpu
將控制權給了另一個執行緒t2
。t2
執行緒讀到的var
也是 10,t1
和t2
都把var
加到 11,當時我們期望的是t1 t2
兩個執行緒使var
+ 2 變成 12。在這裡就有了資源競爭,相同的情況也可能發生在其它的執行緒間,所以出現了最後的結果小於 50 的情況。
為了避免執行緒不同步造成資料不同步,可以對資源進行加鎖。也就是訪問資源的執行緒需要獲得鎖,才能訪問。threading
模組提供了一個 Lock
功能,修改程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var = 0 lock = Lock() #建立鎖 class IncreThread(Thread): def run(self): global var lock.acquire() #獲取鎖 print 'before,var is ',var var += 1 print 'after,var is ',var lock.release() #釋放鎖 def use_incre_thread(): threads = [] for i in range(50): t = IncreThread() threads.append(t) t.start() for t in threads: t.join() print 'After 10 times,var is ',var if __name__ == '__main__': use_incre_thread() |
雖然執行緒可以共享記憶體,但是一個執行緒不能影響其他執行緒內的變數(非全域性變數)。
4,死鎖
線上程間共享多個資源的時候,如果兩個執行緒分別佔有一部分資源並且同時等待對方的資源,就會造成死鎖。儘管死鎖很少發生,但一旦發生就會造成應用的停止響應。下面是一個死鎖的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
mutex_a = Lock() mutex_b = Lock() class MyThread(Thread): def task_b(self): if mutex_a.acquire(): print 'thread get a mutex_a',self.name time.sleep(1) if mutex_b.acquire(): print 'get a mutex_b',self.name mutex_b.release() mutex_a.release() def task_a(self): if mutex_b.acquire(): print 'thread get a mutex_b',self.name time.sleep(1) if mutex_a.acquire(): print 'get a mutex_a',self.name mutex_a.release() mutex_b.release() def run(self): self.task_a() self.task_b() if __name__ == '__main__': threads = [MyThread() for i in range(2)] print threads for t in threads: t.start() |
執行緒需要執行兩個任務,兩個任務都需要獲取鎖,當兩個任務得到鎖後,就需要等另外鎖釋放。
5,可重入鎖
為了支援在同一執行緒中多次請求同一資源,python
提供了可重入鎖(RLock
)。RLock
內部維護著一個Lock
和一個counter
變數,counter
記錄了acquire
的次數,從而使得資源可以被多次require
。直到一個執行緒所有的acquire
都被release
,其他的執行緒才能獲得資源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
mutex = threading.RLock() class MyThread(threading.Thread): def run(self): if mutex.acquire(1): print 'threading gte mutex:',self.name time.sleep(1) mutex.acquire() mutex.release() mutex.release() def main(): print 'start main threading:' threads = [MyThread() for i in range(2)] for t in threads: t.start() for t in threads: t.join() print 'end main threading.' if __name__ == '__main__': main() |
6,後臺執行緒
使用多執行緒預設情況下,當主執行緒退出之後,即使子執行緒沒有 join
,子執行緒也依然會繼續執行。如果希望主執行緒退出後,其子執行緒也退出而不再執行,則需要設定子執行緒為後臺執行緒。python
提供了setDaemon
方法,將子執行緒與主執行緒進行繫結,當主執行緒退出時子執行緒的生命也隨之結束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class MyThread(threading.Thread): def run(self): wait_time = random.randrange(1, 10) print 'thread %s will wait %s s' %(self.name, wait_time) time.sleep(wait_time) time.sleep(30) print 'thread %s finished.' % self.name def main(): print 'start thread:' for i in range(3): t = MyThread() t.setDaemon(1) t.start() print 'end thread.' if __name__ == '__main__': main() |
執行結果:
1 2 3 4 5 |
start thread: thread Thread-1 will wait 9 s thread Thread-2 will wait 1 s thread Thread-3 will wait 7 s end thread. |
本來子執行緒需要等待幾秒才能結束,但是主執行緒提前結束了,所以子執行緒也隨主執行緒結束了。