Python 快速教程(標準庫08):多執行緒與同步 (threading包)

發表於2015-11-12

Python主要通過標準庫中的threading包來實現多執行緒。在當今網路時代,每個伺服器都會接收到大量的請求。伺服器可以利用多執行緒的方式來處理這些請求,以提高對網路埠的讀寫效率。Python是一種網路伺服器的後臺工作語言 (比如豆瓣網),所以多執行緒也就很自然被Python語言支援。

(關於多執行緒的原理和C實現方法,請參考我之前寫的Linux多執行緒與同步,要了解race condition, mutex和condition variable的概念)

 

多執行緒售票以及同步

我們使用Python來實現Linux多執行緒與同步文中的售票程式。我們使用mutex (也就是Python中的Lock類物件) 來實現執行緒的同步:

我們使用了兩個全域性變數,一個是i,用以儲存剩餘票數;一個是lock物件,用於同步執行緒對i的修改。此外,在最後的for迴圈中,我們總共設定了10個執行緒。每個執行緒都執行booth()函式。執行緒在呼叫start()方法的時候正式啟動 (實際上,計算機中最多會有11個執行緒,因為主程式本身也會佔用一個執行緒)。Python使用threading.Thread物件來代表執行緒,用threading.Lock物件來代表一個互斥鎖 (mutex)。

有兩點需要注意:

  • 我們在函式中使用global來宣告變數為全域性變數,從而讓多執行緒共享i和lock (在C語言中,我們通過將變數放在所有函式外面來讓它成為全域性變數)。如果不這麼宣告,由於i和lock是不可變資料物件,它們將被當作一個區域性變數(參看Python動態型別)。如果是可變資料物件的話,則不需要global宣告。我們甚至可以將可變資料物件作為引數來傳遞給執行緒函式。這些執行緒將共享這些可變資料物件。
  • 我們在booth中使用了兩個doChore()函式。可以在未來改程式序,以便讓執行緒除了進行i=i-1之外,做更多的操作,比如列印剩餘票數,找錢,或者喝口水之類的。第一個doChore()依然在Lock內部,所以可以安全地使用共享資源 (critical operations, 比如列印剩餘票數)。第二個doChore()時,Lock已經被釋放,所以不能再去使用共享資源。這時候可以做一些不使用共享資源的操作 (non-critical operation, 比如找錢、喝水)。我故意讓doChore()等待了0.5秒,以代表這些額外的操作可能花費的時間。你可以定義的函式來代替doChore()。

 

OOP建立執行緒

上面的Python程式非常類似於一個程式導向的C程式。我們下面介紹如何通過物件導向 (OOP, object-oriented programming,參看Python物件導向的基本概念Python物件導向的進一步擴充) 的方法實現多執行緒,其核心是繼承threading.Thread類。我們上面的for迴圈中已經利用了threading.Thread()的方法來建立一個Thread物件,並將函式booth()以及其引數傳遞給改物件,並呼叫start()方法來執行執行緒。OOP的話,通過修改Thread類的run()方法來定義執行緒所要執行的命令。

 

我們自己定義了一個類BoothThread, 這個類繼承自thread.Threading類。然後我們把上面的booth()所進行的操作統統放入到BoothThread類的run()方法中。注意,我們沒有使用全域性變數宣告global,而是使用了一個詞典monitor存放全域性變數,然後把詞典作為引數傳遞給執行緒函式。由於詞典是可變資料物件,所以當它被傳遞給函式的時候,函式所使用的依然是同一個物件,相當於被多個執行緒所共享。這也是多執行緒乃至於多程式程式設計的一個技巧 (應儘量避免上面的global宣告的用法,因為它並不適用於windows平臺)。

上面OOP程式設計方法與程式導向的程式設計方法相比,並沒有帶來太大實質性的差別。

 

其他

threading.Thread物件: 我們已經介紹了該物件的start()和run(), 此外:

  • join()方法,呼叫該方法的執行緒將等待直到改Thread物件完成,再恢復執行。這與程式間呼叫wait()函式相類似。

 

下面的物件用於處理多執行緒同步。物件一旦被建立,可以被多個執行緒共享,並根據情況阻塞某些程式。請與Linux多執行緒與同步中的同步工具參照閱讀。

threading.Lock物件: mutex, 有acquire()和release()方法。

threading.Condition物件: condition variable,建立該物件時,會包含一個Lock物件 (因為condition variable總是和mutex一起使用)。可以對Condition物件呼叫acquire()和release()方法,以控制潛在的Lock物件。此外:

  • wait()方法,相當於cond_wait()
  • notify_all(),相當與cond_broadcast()
  • nofify(),與notify_all()功能類似,但只喚醒一個等待的執行緒,而不是全部

threading.Semaphore物件: semaphore,也就是計數鎖(semaphore傳統意義上是一種程式間同步工具,見Linux程式間通訊)。建立物件的時候,可以傳遞一個整數作為計數上限 (sema = threading.Semaphore(5))。它與Lock類似,也有Lock的兩個方法。

threading.Event物件: 與threading.Condition相類似,相當於沒有潛在的Lock保護的condition variable。物件有True和False兩個狀態。可以多個執行緒使用wait()等待,直到某個執行緒呼叫該物件的set()方法,將物件設定為True。執行緒可以呼叫物件的clear()方法來重置物件為False狀態。

 

練習

參照Linux多執行緒與同步中的condition variable的例子,使用Python實現。同時考慮使用程式導向和麵向物件的程式設計方法。

更多的threading的內容請參考:

http://docs.python.org/library/threading.html

 

總結

threading.Thread

Lock, Condition, Semaphore, Event

相關文章