Python標準庫08 多線程與同步 (threading包)

做者:Vamei 出處:http://www.cnblogs.com/vamei 歡迎轉載,也請保留這段聲明。謝謝!html

 

Python主要經過標準庫中的threading包來實現多線程。在當今網絡時代,每一個服務器都會接收到大量的請求。服務器能夠利用多線程的方式來處理這些請求,以提升對網絡端口的讀寫效率。Python是一種網絡服務器的後臺工做語言 (好比豆瓣網),因此多線程也就很天然被Python語言支持。python

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

 

多線程售票以及同步

咱們使用Python來實現Linux多線程與同步文中的售票程序。咱們使用mutex (也就是Python中的Lock類對象) 來實現線程的同步:windows

# A program to simulate selling tickets in multi-thread way
# Written by Vamei

import threading
import time
import os

# This function could be any function to do other chores.
def doChore():
    time.sleep(0.5)

# Function for each thread
def booth(tid):
    global i
    global lock
    while True:
        lock.acquire()                # Lock; or wait if other thread is holding the lock
        if i != 0:
            i = i - 1                 # Sell tickets
            print(tid,':now left:',i) # Tickets left
            doChore()                 # Other critical operations
        else:
            print("Thread_id",tid," No more tickets")
            os._exit(0)              # Exit the whole process immediately
        lock.release()               # Unblock
        doChore()                    # Non-critical operations

# Start of the main function
i    = 100                           # Available ticket number 
lock = threading.Lock()              # Lock (i.e., mutex)

# Start 10 threads
for k in range(10):
    new_thread = threading.Thread(target=booth,args=(k,))   # Set up thread; target: the callable (function) to be run, args: the argument for the callable 
    new_thread.start()                                      # run the thread


咱們使用了兩個全局變量,一個是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()方法來定義線程所要執行的命令。函數

# A program to simulate selling tickets in multi-thread way
# Written by Vamei

import threading
import time
import os

# This function could be any function to do other chores.
def doChore():
    time.sleep(0.5)

# Function for each thread
class BoothThread(threading.Thread):
    def __init__(self, tid, monitor):
        self.tid          = tid
        self.monitor = monitor
        threading.Thread.__init__(self)
    def run(self):
        while True:
            monitor['lock'].acquire()                          # Lock; or wait if other thread is holding the lock
            if monitor['tick'] != 0:
                monitor['tick'] = monitor['tick'] - 1          # Sell tickets
                print(self.tid,':now left:',monitor['tick'])   # Tickets left
                doChore()                                      # Other critical operations
            else:
                print("Thread_id",self.tid," No more tickets")
                os._exit(0)                                    # Exit the whole process immediately
            monitor['lock'].release()                          # Unblock
            doChore()                                          # Non-critical operations

# Start of the main function
monitor = {'tick':100, 'lock':threading.Lock()}

# Start 10 threads
for k in range(10):
    new_thread = BoothThread(k, monitor)
    new_thread.start()


咱們本身定義了一個類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

相關文章
相關標籤/搜索