Python 多執行緒

人世間發表於2016-05-24

執行緒和程式

計算機,用於計算的機器。計算機的核心是CPU,在現在多核心的電腦很常見了。為了充分利用cpu核心做計算任務,程式實現了多執行緒模型。通過多執行緒實現多工的並行執行。

現在的作業系統多是多工作業系統。每個應用程式都有一個自己的程式。作業系統會為這些程式分配一些執行資源,例如記憶體空間等。在程式中,又可以建立一些執行緒,他們共享這些記憶體空間,並由作業系統呼叫,以便平行計算。

執行緒狀態

建立執行緒之後,執行緒並不是始終保持一個狀態。其狀態大概如下:

  • New 建立。
  • Runnable 就緒。等待排程
  • Running 執行。
  • Blocked 阻塞。阻塞可能在 Wait Locked Sleeping
  • Dead 消亡

這些狀態之間是可以相互轉換的,一圖勝千顏色:

threading_state

threading_state

(圖片引用 內心求法部落格)

執行緒中執行到阻塞,可能有3種情況:

  • 同步:執行緒中獲取同步鎖,但是資源已經被其他執行緒鎖定時,進入Locked狀態,直到該資源可獲取(獲取的順序由Lock佇列控制)
  • 睡眠:執行緒執行sleep()或join()方法後,執行緒進入Sleeping狀態。區別在於sleep等待固定的時間,而join是等待子執行緒執行完。當然join也可以指定一個“超時時間”。從語義上來說,如果兩個執行緒a,b, 在a中呼叫b.join(),相當於合併(join)成一個執行緒。最常見的情況是在主執行緒中join所有的子執行緒。
  • 等待:執行緒中執行wait()方法後,執行緒進入Waiting狀態,等待其他執行緒的通知(notify)。

執行緒型別

執行緒有著不同的狀態,也有不同的型別。大致可分為:

  • 主執行緒
  • 子執行緒
  • 守護執行緒(後臺執行緒)
  • 前臺執行緒

Python執行緒與GIL

相比程式,執行緒更加輕量,可以實現併發。可是在python的世界裡,對於執行緒,就不得不說一句GIL(全域性直譯器鎖)。GIL的存在讓python的多執行緒多少有點雞肋了。Cpython的執行緒是作業系統原生的執行緒在直譯器解釋執行任何Python程式碼時,都需要先獲得這把鎖才行,在遇到 I/O 操作時會釋放這把鎖。因為python的程式做為一個整體,直譯器程式內只有一個執行緒在執行,其它的執行緒都處於等待狀態等著GIL的釋放。

關於GIL可以有更多的趣事,一時半會都說不完。總之python想用多執行緒併發,效果可能還不如單執行緒(執行緒切換耗時間)。想要利用多核,可以考慮使用多程式。

執行緒的建立

雖然python執行緒比較雞肋,可是也併發一無是處。多瞭解還是有理由對併發模型的理解。

Python提供兩個模組進行多執行緒的操作,分別是threadthreading,前者是比較低階的模組,用於更底層的操作,一般應有級別的開發不常用。後者則封裝了更多高階的介面,類似java的多執行緒風格,提供run方法和start呼叫。

輸入如下:(不同的環境不一樣)

每個執行緒都依次列印 0 – 3 三個數字,可是從輸出的結果觀察,執行緒並不是順序的執行,而是三個執行緒之間相互交替執行。此外,我們的主執行緒執行結束,將會列印 End Main threading。從輸出結果可以知道,主執行緒結束後,新建的執行緒還在執行。

執行緒合併(join方法)

上述的例子中,主執行緒結束了,子執行緒還在執行。如果需要主執行緒等待子執行緒執行完畢再退出,可是使用執行緒的join方法。join方法官網文件大概是

join(timeout)方法將會等待直到執行緒結束。這將阻塞正在呼叫的執行緒,直到被呼叫join()方法的執行緒結束。

主執行緒或者某個函式如果建立了子執行緒,只要呼叫了子執行緒的join方法,那麼主執行緒就會被子執行緒所阻塞,直到子執行緒執行完畢再輪到主執行緒執行。其結果就是所有子執行緒執行完畢,才列印 End Main threading。只需要修改上面的main函式

輸入如下:

所有子執行緒結束了才會執行也行print "End Main threading"。有人會這麼想,如果在 t.start()之後join會怎麼樣?結果也能阻塞主執行緒,但是每個執行緒都是依次執行,變得有順序了。其實join很好理解,就字面上的意思就是子執行緒 “加入”(join)主執行緒嘛。在CPU執行時間片段上“等於”主執行緒的一部分。在start之後join,也就是每個子執行緒由被後來新建的子執行緒給阻塞了,因此執行緒之間變得有順序了。

借用moxie的總結:

1 join方法的作用是阻塞主程式(擋住,無法執行join以後的語句),專注執行多執行緒。

2 多執行緒多join的情況下,依次執行各執行緒的join方法,前頭一個結束了才能執行後面一個。

3 無引數,則等待到該執行緒結束,才開始執行下一個執行緒的join。

4 設定引數後,則等待該執行緒這麼長時間就不管它了(而該執行緒並沒有結束)。不管的意思就是可以執行後面的主程式了

執行緒同步與互斥鎖

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

輸出結果如下,十個執行緒,每個執行緒增加100,運算結果應該是1000:

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

死鎖

有鎖就可以方便處理執行緒同步問題,可是多執行緒的複雜度和難以除錯的根源也來自於執行緒的鎖。利用不當,甚至會帶來更多問題。比如死鎖就是需要避免的問題。

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

可重入鎖

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

條件變數

實用鎖可以達到執行緒同步,前面的互斥鎖就是這種機制。更復雜的環境,需要針對鎖進行一些條件判斷。Python提供了Condition物件。它除了具有acquire和release方法之外,還提供了wait和notify方法。執行緒首先acquire一個條件變數鎖。如果條件不足,則該執行緒wait,如果滿足就執行執行緒,甚至可以notify其他執行緒。其他處於wait狀態的執行緒接到通知後會重新判斷條件。

條件變數可以看成不同的執行緒先後acquire獲得鎖,如果不滿足條件,可以理解為被扔到一個(Lock或RLock)的waiting池。直達其他執行緒notify之後再重新判斷條件。該模式常用於生成消費者模式:

上述就是一個簡單的生產者消費模型,先看生產者,生產者條件變數鎖之後就檢查條件,如果不符合條件則wait,wait的時候會釋放鎖。如果條件符合,則往佇列新增元素,然後會notify其他執行緒。注意生產者呼叫了condition的notify()方法後,消費者被喚醒,但喚醒不意味著它可以開始執行,notify()並不釋放lock,呼叫notify()後,lock依然被生產者所持有。生產者通過con.release()顯式釋放lock。消費者再次開始執行,獲得條件鎖然後判斷條件執行。

佇列

生產消費者模型主要是對佇列程式操作,貼心的Python為我們實現了一個佇列結構,佇列內部實現了鎖的相關設定。可以用佇列重寫生產消費者模型。

queue內部實現了相關的鎖,如果queue的為空,則get元素的時候會被阻塞,知道佇列裡面被其他執行緒寫入資料。同理,當寫入資料的時候,如果元素個數大於佇列的長度,也會被阻塞。也就是在 put 或 get的時候都會獲得Lock。

執行緒通訊

執行緒可以讀取共享的記憶體,通過記憶體做一些資料處理。這就是執行緒通訊的一種,python還提供了更加高階的執行緒通訊介面。Event物件可以用來進行執行緒通訊,呼叫event物件的wait方法,執行緒則會阻塞等待,直到別的執行緒set之後,才會被喚醒。

上面的例子建立了3個執行緒,呼叫執行緒之後,執行緒將會被阻塞,sleep 3秒後,才會被喚醒執行,大概輸出如下:

後臺執行緒

預設情況下,主執行緒退出之後,即使子執行緒沒有join。那麼主執行緒結束後,子執行緒也依然會繼續執行。如果希望主執行緒退出後,其子執行緒也退出而不再執行,則需要設定子執行緒為後臺執行緒。python提供了seDeamon方法:

輸出結果如下:

每個執行緒都應該等待sleep幾秒,可是主執行緒很快就執行完了,子執行緒因為設定了後臺執行緒,所以也跟著主執行緒退出了。

關於Python多執行緒的介紹暫且就這些,多執行緒用於併發任務。對於併發模型,Python還有比執行緒更好的方法。同樣設計任務的時候,也需要考慮是計算密集型還是IO密集型。針對不同的場景,設計不同的程式系統。

文中的程式碼 learn-threading

參考資料:

1 http://www.cnblogs.com/holbrook/tag/%E5%A4%9A%E7%BA%BF%E7%A8%8B/
2 http://zhuoqiang.me/python-thread-gil-and-ctypes.html

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

任選一種支付方式

Python 多執行緒 Python 多執行緒

相關文章