1. 程式人生 > 其它 >生產者 消費者模式

生產者 消費者模式

技術標籤:設計模式python面向物件開發python多執行緒設計模式佇列

生產者 消費者模式

開發中的生產者和消費者模式 和生活中的生產者消費者 類似

都是一種生產消費關係 生產者產出 供給給消費者使用

在實際的軟體開發過程中,經常會碰到如下場景:某個模組負責產生資料,這些資料由另一個模組來負責處理(此處的模組是廣義的,可以是類、函式、執行緒、程序等)。產生資料的模組,就形象地稱為生產者;而處理資料的模組,就稱為消費者。

單單抽象出生產者和消費者,還夠不上是生產者/消費者模式。該模式還需要有一個緩衝區處於生產者和消費者之間,作為一箇中介。生產者把資料放入緩衝區,而消費者從緩衝區取出資料。大概的結構如下圖。

img

緩衝區作用

解 耦

假設生產者和消費者分別是兩個類。如果讓生產者直接呼叫消費者的某個方法,那麼生產者對於消費者就會產生依賴(也就是耦合)。將來如果消費者的程式碼發生變化,可能會影響到生產者。而如果兩者都依賴於某個緩衝區,兩者之間不直接依賴,耦合也就相應降低了。

支援併發

生產者直接呼叫消費者的某個方法,還有另一個弊端。由於函式呼叫是同步的(或者叫阻塞的),在消費者的方法沒有返回之前,生產者只好一直等在那邊。萬一消費者處理資料很慢,生產者就會白白糟蹋大好時光。使用了生產者/消費者模式之後,生產者和消費者可以是兩個獨立的併發主體(常見併發型別有程序和執行緒兩種,後面的帖子會講兩種併發型別下的應用)。生產者把製造出來的資料往緩衝區一丟,就可以再去生產下一個資料。基本上不用依賴消費者的處理速度。其實當初這個模式,主要就是用來處理併發問題的。

在使用中我們通常用 一個佇列來作為緩衝區 生產者將資料放在佇列中 消費者從佇列中取出資料

上一個例子

這是爬取某個網站圖片 的爬蟲程式碼 使用的是生產者消費者模式

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()