【Python爬蟲學習筆記10】多線程中的生產者消費者模式
在多線程編程中,最經典的模式是生產者消費者模式。其中,生產者是專門用來生產數據的線程,它把數據存放在一個中間變量中;而消費者則從這個中間變量取出數據進行消費。由於生產者和消費者共享中間變量,這些變量大多是全局的,因此需要使用鎖來保證數據完整性,防止多線程共享問題的產生。
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】多線程中的生產者消費者模式