理解 python 中多執行緒

發表於2016-11-29

雖然python中由於GIL的機制致使多執行緒不能利用機器多核的特性,但是多執行緒對於我們理解併發模型以及底層操作非常有用。

執行緒的有兩種使用方法,一種是在函式使用,一種是放在類中使用

1,在函式中使用多執行緒

語法如下:

引數說明:

下面是一個例子:

執行結果:

2,在類中多使用執行緒

下面是在類中使用執行緒的示例:

  • run(),需要重寫,編寫程式碼實現所需要的功能。
  • getName(),獲得執行緒物件名稱
  • setName(),設定執行緒物件名稱
  • start(),啟動執行緒
  • join([timeout]),等待另一執行緒結束後再執行。
  • setDaemon(bool),設定子執行緒是否隨主執行緒一起結束,必須在start() 之前呼叫,預設為False
  • isDaemon(),判斷執行緒是否隨主執行緒一起結束。
  • isAlive(),檢查執行緒是否在執行中。

join方法的作用是阻塞主程式(無法執行join以後的語句),主執行緒等待這個執行緒結束後,才可以執行下一條指令。多執行緒多join的情況下,依次執行各執行緒的join方法,前頭一個結束了才能執行後面一個。無引數,則等待到該執行緒結束,才開始執行下一個執行緒的join。設定引數後,則等待該執行緒這麼長時間就不管它了(而該執行緒並沒有結束)。不管的意思就是可以執行後面的主程式了。

3,執行緒同步與互斥鎖

執行緒之所以比程式輕量,其中一個原因就是他們共享記憶體。也就是各個執行緒可以平等的訪問記憶體的資料,如果在短時間“同時並行”讀取修改記憶體的資料,很可能造成資料不同步。例如下面的例子:

有一個全域性變數var,五十個執行緒,每個執行緒對var變數進行加 1 運算,但是當你多執行幾次後,發現並不是每次的執行結果都是 50,為什麼呢?

var是 10 的時候,執行緒t1讀取了var,這個時刻cpu將控制權給了另一個執行緒t2t2執行緒讀到的var也是 10,t1t2都把var加到 11,當時我們期望的是t1 t2兩個執行緒使var + 2 變成 12。在這裡就有了資源競爭,相同的情況也可能發生在其它的執行緒間,所以出現了最後的結果小於 50 的情況。

為了避免執行緒不同步造成資料不同步,可以對資源進行加鎖。也就是訪問資源的執行緒需要獲得鎖,才能訪問。threading 模組提供了一個 Lock 功能,修改程式碼如下:

雖然執行緒可以共享記憶體,但是一個執行緒不能影響其他執行緒內的變數(非全域性變數)。

4,死鎖

線上程間共享多個資源的時候,如果兩個執行緒分別佔有一部分資源並且同時等待對方的資源,就會造成死鎖。儘管死鎖很少發生,但一旦發生就會造成應用的停止響應。下面是一個死鎖的例子:

執行緒需要執行兩個任務,兩個任務都需要獲取鎖,當兩個任務得到鎖後,就需要等另外鎖釋放。

5,可重入鎖

為了支援在同一執行緒中多次請求同一資源,python 提供了可重入鎖(RLock)。RLock內部維護著一個Lock和一個counter變數,counter記錄了acquire的次數,從而使得資源可以被多次require。直到一個執行緒所有的acquire都被release,其他的執行緒才能獲得資源。

6,後臺執行緒

使用多執行緒預設情況下,當主執行緒退出之後,即使子執行緒沒有 join,子執行緒也依然會繼續執行。如果希望主執行緒退出後,其子執行緒也退出而不再執行,則需要設定子執行緒為後臺執行緒。python提供了setDaemon方法,將子執行緒與主執行緒進行繫結,當主執行緒退出時子執行緒的生命也隨之結束。

執行結果:

本來子執行緒需要等待幾秒才能結束,但是主執行緒提前結束了,所以子執行緒也隨主執行緒結束了。

相關文章