生產者 消費者模式
阿新 • • 發佈:2021-02-10
技術標籤:設計模式python面向物件開發python多執行緒設計模式佇列
生產者 消費者模式
開發中的生產者和消費者模式 和生活中的生產者消費者 類似
都是一種生產消費關係 生產者產出 供給給消費者使用
在實際的軟體開發過程中,經常會碰到如下場景:某個模組負責產生資料,這些資料由另一個模組來負責處理(此處的模組是廣義的,可以是類、函式、執行緒、程序等)。產生資料的模組,就形象地稱為生產者;而處理資料的模組,就稱為消費者。
單單抽象出生產者和消費者,還夠不上是生產者/消費者模式。該模式還需要有一個緩衝區處於生產者和消費者之間,作為一箇中介。生產者把資料放入緩衝區,而消費者從緩衝區取出資料。大概的結構如下圖。
緩衝區作用
解 耦
假設生產者和消費者分別是兩個類。如果讓生產者直接呼叫消費者的某個方法,那麼生產者對於消費者就會產生依賴(也就是耦合)。將來如果消費者的程式碼發生變化,可能會影響到生產者。而如果兩者都依賴於某個緩衝區,兩者之間不直接依賴,耦合也就相應降低了。
支援併發
生產者直接呼叫消費者的某個方法,還有另一個弊端。由於函式呼叫是同步的(或者叫阻塞的),在消費者的方法沒有返回之前,生產者只好一直等在那邊。萬一消費者處理資料很慢,生產者就會白白糟蹋大好時光。使用了生產者/消費者模式之後,生產者和消費者可以是兩個獨立的併發主體(常見併發型別有程序和執行緒兩種,後面的帖子會講兩種併發型別下的應用)。生產者把製造出來的資料往緩衝區一丟,就可以再去生產下一個資料。基本上不用依賴消費者的處理速度。其實當初這個模式,主要就是用來處理併發問題的。
在使用中我們通常用 一個佇列來作為緩衝區 生產者將資料放在佇列中 消費者從佇列中取出資料
上一個例子
這是爬取某個網站圖片 的爬蟲程式碼 使用的是生產者消費者模式
import threading
import requests
from lxml import etree
from queue import Queue
import re
import os
from urllib import request
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36" ,
}
# 生產者 獲取圖片連結 放入佇列中
class Producer(threading.Thread):
def __init__(self,page_queue,image_quque,*args,**kwargs):
super(Producer, self).__init__()
self.page_queue = page_queue
self.image_quque = image_quque
def run(self):
while True:
# 判斷佇列是否為空 為空返回真 .empty
if self.page_queue.empty():
break
else:
# 取出佇列中的page連結
url = self.page_queue.get()
# 呼叫 parse獲取圖片連結
self.parse_page(url)
def parse_page(self,url):
response = requests.get(url,headers=headers)
text = response.text
html = etree.HTML(text)
imgs = html.xpath('//div[@class="page-content text-center"]//img')
for img in imgs:
img_url = img.get('data-original')
alt = img.get('alt')
# print(type(alt))
alt = re.sub(r'[\??\.,。!!\/]','',alt)
suffix = os.path.splitext(img_url)[1]
filename = alt+suffix
# 判斷圖片佇列是否滿 沒有滿就繼續放入
if not self.image_quque.full():
self.image_quque.put((filename,img_url))
# 消費者 從佇列中拿出連結 獲取圖片儲存
class Consumer(threading.Thread):
def __init__(self,page_queue,img_queue,*args,**kwargs):
super(Consumer, self).__init__()
self.page_queue = page_queue
self.img_queue = img_queue
def run(self):
while True:
if self.page_queue.empty() and self.img_queue().empty():
break
filenama,img_url = self.img_queue.get()
request.urlretrieve(img_url,'sources/'+filenama)
print(filenama,'下載完成')
def main():
page_queue = Queue(100)
image_queue = Queue(1000)
for x in range(1, 101):
url = 'http://www.doutula.com/photo/list/?page=%d' % x
# 將需要爬取的頁面都放入queue佇列
page_queue.put(url)
# 建立生產者程序
for i in range(5):
p1 = Producer(page_queue, image_queue)
p1.start()
# 建立消費者程序
for i in range(5):
c1 = Consumer(page_queue,image_queue)
c1.start()
if __name__ == '__main__':
main()