1. 程式人生 > >【Python爬蟲學習筆記10】多線程中的生產者消費者模式

【Python爬蟲學習筆記10】多線程中的生產者消費者模式

其中 因此 問題 共享 and 生產者消費者模式 共享問題 由於 接下來

在多線程編程中,最經典的模式是生產者消費者模式。其中,生產者是專門用來生產數據的線程,它把數據存放在一個中間變量中;而消費者則從這個中間變量取出數據進行消費。由於生產者和消費者共享中間變量,這些變量大多是全局的,因此需要使用鎖來保證數據完整性,防止多線程共享問題的產生。

Python threading模塊為我們提供了兩種實現生產者消費者模式的方案,分別基於Lock類和Condition類。在具體介紹前,我們先來說明一下我們的生產者消費者模型背景:我們這裏有一個金庫gMoney,若幹個生產者Producer和消費者Consumer,其中生產者負責向金庫裏存錢,而消費者則從金庫中取錢;當生產者存放10次錢以後則不再存錢,而消費者所取的錢只要沒有超過金庫剩余便可一直取。

接下來我們我們對兩種方案的實現進行介紹。

使用Lock類實現

Lock類實現方案采用的是threading中的鎖機制,通過對共享變量操作加鎖和釋放鎖來確保數據的統一性,具體程序如下:

## 使用Lock類實現生產者消費者模式
import threading
import random,time

gMoney = 1000  # 定義全局金庫、
gLock = threading.Lock()  # 實例化對金庫操作的鎖
gTotaltimes = 10  # 存錢總次數和
gTimes = 0     # 存錢當前次數


# 生產者線程類
class Producer(threading.Thread):
    
def run(self): global gMoney, gTimes while True: # 循環存錢操作 if gTimes >= gTotaltimes: return # 當存錢次數上限時結束線程 money = random.randint(100, 1000) # 存錢數 gLock.acquire() # 對金庫操作加鎖 gMoney += money print(%s produces $%s,the balance is %s .
% (threading.current_thread(), money, gMoney)) gTimes += 1 gLock.release() # 對金庫操作釋放鎖 time.sleep(0.5) # 消費者線程類 class Consumer(threading.Thread): def run(self): global gMoney while True: # 循環取錢操作 money = random.randint(100, 1000) # 取錢數 if gMoney >= money: # 金庫錢充足 gLock.acquire() # 對金庫操作加鎖 gMoney -= money print(%s consumes $%s,the balance is %s . % (threading.current_thread(), money, gMoney)) gLock.release() # 對金庫操作釋放鎖 time.sleep(0.5) else: # 金庫錢不足 if gTimes >= gTotaltimes: # 若已達存錢次數上限,則退出 return print(%s wants to consume $%s,while the balance is %s . % (threading.current_thread(), money, gMoney)) # 開啟兩個生產者線程和1個消費者線程 def main(): for x in range(2): t = Producer() t.start() for x in range(1): t = Consumer() t.start() if __name__ == __main__: main()

使用Condition類實現

在threading模塊中的另一個較優的解決方案是使用Condition類,其可以在沒有數據的時候自動地處於阻塞等待狀態,而一旦有合適的數據則可以使用notify相關函數來通知處於等待狀態的線程(此時並不會使其直接執行)。這樣一來就可以不用做一些無用的上鎖和解鎖操作,提高程序的性能。

在實現生產者消費者模式前,先來說明一下Condition類的幾個常用方法:

1.acquire():上鎖。
2.release():釋放鎖。
3.wait():將當前線程處於等待狀態,並且會自動釋放其已占有的鎖。該線程可以被其他線程使用notify()和notify_all()喚醒,被喚醒後會繼續自動等待上鎖,上鎖後再執行後續代碼。
4.notify()/notify_all():通知某個/所有正在等待的線程。這兩個方法都不會釋放鎖,並且需要在release()之前調用。

和Lock方案類似,僅做了少數地方的修改,具體實現如下:

## 使用Condition類實現生產者消費者模式
import threading
import random, time

gMoney = 1000
gCondition = threading.Condition()  # 實例化Condition類
gTotalTimes = 10
gTimes = 0


# 生產者線程類
class Producer(threading.Thread):
    def run(self):
        global gMoney, gTimes
        while True:
            if gTimes >= gTotalTimes: return
            money = random.randint(100, 1000)
            gCondition.acquire()  # 加鎖
            gMoney += money
            print(%s produces $%s,the balance is %s . % (threading.current_thread(), money, gMoney))
            gTimes += 1
            gCondition.release()  # 釋放鎖
            time.sleep(0.5)


# 消費者線程類
class Consumer(threading.Thread):
    def run(self):
        global gMoney
        while True:
            money = random.randint(100, 1000)
            gCondition.acquire()  # 加鎖
            while gMoney < money:  # 金庫不足
                if gTimes >= gTotalTimes:
                    gCondition.release()  # 如果達到存錢次數上限則釋放鎖並退出
                    return
                print(%s wants to consume $%s,while the balance is %s . % (threading.current_thread(), money, gMoney))
                gCondition.wait()  # 不足情況下進入等待
            gMoney -= money
            print(%s consumes $%s,the balance is %s . % (threading.current_thread(), money, gMoney))
            gCondition.release()  # 釋放鎖
            time.sleep(0.5)


# 開啟線程
def main():
    for x in range(2):
        t = Producer()
        t.start()
    for x in range(1):
        t = Consumer()
        t.start()


if __name__ == __main__:
    main()


【Python爬蟲學習筆記10】多線程中的生產者消費者模式