標籤:位移量 target break 緩衝區 producer color dom random 實現
一、為什麼要使用生產者和消費者?
線上程世界裡,生產者就是生產資料的線程,消費者就是消費資料的線程,在多線程開發當中,如果生產者處理速度很快,而消費者處理速度很慢,那麼生產者就必須等待消費者處理完,才能繼續生產資料,同樣的道理,如果消費者的處理能力大於生產者,那麼消費者就必須等待生產者,為瞭解決這個問題於是引入了生產者和消費者模式。
二、什麼是生產者消費者模式
生產者消費者模式是通過一個容器來解決生產者和消費者的強耦合問題。生產者和消費者彼此之間不直接通訊,而通過阻塞隊列來進行通訊,所以生產者生產資料之後不用等待消費者處理,直接扔給阻塞隊列,消費者不找生產者要資料,而是直接從阻塞隊列裡取,阻塞隊列就相當於一個緩衝區,平衡了生產者和消費者的處理能力。
from multiprocessing import Process,Queueimport time,random,osdef consumer(q): while True: res=q.get() if res is None:break #收到結束訊號則結束 time.sleep(random.randint(1,3)) print(‘\033[45m%s 吃 %s\033[0m‘ %(os.getpid(),res))def producer(q): for i in range(10): time.sleep(random.randint(1,3)) res=‘包子%s‘ %i q.put(res) print(‘\033[44m%s 生產了 %s\033[0m‘ %(os.getpid(),res)) q.put(None) #發送結束訊號if __name__ == ‘__main__‘: q=Queue() #生產者們:即廚師們 p1=Process(target=producer,args=(q,)) #消費者們:即吃貨們 c1=Process(target=consumer,args=(q,)) #開始 p1.start() c1.start() print(‘主‘)
基於隊列實現生產者消費者模型
注意:結束訊號None,不一定要由生產者發,主進程裡同樣可以發,但主進程需要等生產者結束後才應該發送該訊號
from multiprocessing import Process,Queueimport time,random,osdef consumer(q): while True: res=q.get() if res is None:break #收到結束訊號則結束 time.sleep(random.randint(1,3)) print(‘\033[45m%s 吃 %s\033[0m‘ %(os.getpid(),res))def producer(q): for i in range(2): time.sleep(random.randint(1,3)) res=‘包子%s‘ %i q.put(res) print(‘\033[44m%s 生產了 %s\033[0m‘ %(os.getpid(),res))if __name__ == ‘__main__‘: q=Queue() #生產者們:即廚師們 p1=Process(target=producer,args=(q,)) #消費者們:即吃貨們 c1=Process(target=consumer,args=(q,)) #開始 p1.start() c1.start() p1.join() q.put(None) #發送結束訊號 print(‘主‘)
主進程在生產者生產完畢後發送結束訊號None
from multiprocessing import Process,Queueimport time,random,osdef consumer(q): while True: res=q.get() if res is None:break #收到結束訊號則結束 time.sleep(random.randint(1,3)) print(‘\033[45m%s 吃 %s\033[0m‘ %(os.getpid(),res))def producer(name,q): for i in range(2): time.sleep(random.randint(1,3)) res=‘%s%s‘ %(name,i) q.put(res) print(‘\033[44m%s 生產了 %s\033[0m‘ %(os.getpid(),res))if __name__ == ‘__main__‘: q=Queue() #生產者們:即廚師們 p1=Process(target=producer,args=(‘包子‘,q)) p2=Process(target=producer,args=(‘骨頭‘,q)) p3=Process(target=producer,args=(‘泔水‘,q)) #消費者們:即吃貨們 c1=Process(target=consumer,args=(q,)) c2=Process(target=consumer,args=(q,)) #開始 p1.start() p2.start() p3.start() c1.start() p1.join() #必須保證生產者全部生產完畢,才應該發送結束訊號 p2.join() p3.join() q.put(None) #有幾個消費者就應該發送幾次結束訊號None q.put(None) #發送結束訊號 print(‘主‘)
多個消費者的例子:有幾個消費者就需要發送幾次結束訊號
三、管道
建立管道的類:
Pipe([]duplex):在進程之間建立一條管道,並返回元組(conn1,conn2),其中conn1,conn2表示管道兩端的連線物件,強調一點:必須在產生Process對象之前產生管道
參數:
duplex:預設管道是全雙工系統的,如果將duplex設成False,conn1隻能用於接收,conn2隻能用於發送。
主要方法:
conn1.recv() :接收conn2.send(obj)發送的對象。如果沒有訊息可接收,recv方法會一直阻塞。如果串連的另外一端已經關閉,那麼recv方法會拋出、EOFError。
conn1.send(obj):通過串連發送對象。obj是與序列化相容的任意對象。
conn1.close():關閉串連。如果conn1被記憶體回收,將自動調用此方法。
conn1.fileno():返回串連使用的整數檔案描述符。
conn1.poll([timeout]): 如果串連上的資料可用,返回True。timeout指定等待的最長時限。如果省略此參數,方法將立即返回結果。如果將timeout射成None,操作將無限期地等待資料到達。
conn1.recv_bytes([maxlength]): 接收c.send_bytes()方法發送的一條完整的位元組訊息。maxlength指定要接收的最大位元組數。如果進入的訊息,超過了這個最大值,將引發IOError異常,並且在串連上無法進行進一步讀取。如果串連的另外一端已經關閉,再也不存在任何資料,將引發EOFError異常。
conn.send_bytes(buffer[, offset[,size]]):通過串連發送位元組資料緩衝區,buffer是支援緩衝區介面的任意對象,offset是緩衝區中的位元組位移量,size是要發送位元組數。結果資料以單條訊息的形式發出,然後調用c.recv_bytes()函數進行接收。
conn.recv_bytes_into(buffer[,offset]):接收一條完整的位元組訊息,並把它儲存在buffer對象中,該對象支援可寫入的緩衝區介面(即bytearray對象或類似的對象)。offset指定緩衝區中放置訊息處的位元組位移。傳回值是收到的位元組數。如果訊息長度大於可用的緩衝區空間,將引發BufferTooShort異常。
應該特別注意管道端點的正確管理問題:如果生產者或者消費者中都沒有使用管道的某個端點,就應將它關閉。這也說明了為何在生產者中關閉了管道的輸出端,在消費者中關閉了管道的輸入端。
from multiprocessing import Process,Pipedef consumer(p,name): produce, consume=p produce.close() while True: try: baozi=consume.recv() print(‘%s 收到包子:%s‘ %(name,baozi)) except EOFError: breakdef producer(seq,p): produce, consume=p consume.close() for i in seq: produce.send(i)if __name__ == ‘__main__‘: produce,consume=Pipe() c1=Process(target=consumer,args=((produce,consume),‘c1‘)) c1.start() seq=(i for i in range(10)) producer(seq,(produce,consume)) produce.close() consume.close() c1.join() print(‘主進程‘)
pipe實現生產者消費者模型
from multiprocessing import Process,Pipe,Lockdef consumer(p,name,lock): produce, consume=p produce.close() while True: lock.acquire() baozi=consume.recv() lock.release() if baozi: print(‘%s 收到包子:%s‘ %(name,baozi)) else: consume.close() breakdef producer(p,n): produce, consume=p consume.close() for i in range(n): produce.send(i) produce.send(None) produce.send(None) produce.close()if __name__ == ‘__main__‘: produce,consume=Pipe() lock = Lock() c1=Process(target=consumer,args=((produce,consume),‘c1‘,lock)) c2=Process(target=consumer,args=((produce,consume),‘c2‘,lock)) p1=Process(target=producer,args=((produce,consume),10)) c1.start() c2.start() p1.start() produce.close() consume.close() c1.join() c2.join() p1.join() print(‘主進程‘)
多個消費之之間的競爭問題帶來的資料不安全問題
四、資料之間的資料共用
通過使用線程,推薦做法也是將程式設計為大量獨立的線程集合,通過訊息佇列交換資料。這樣極大地減少了對使用鎖定和其他同步手段的需求,還可以擴充到分布式系統中。但進程間應該盡量避免通訊,即使需要通訊,也應該選擇進程安全的工具來避免加鎖帶來的問題。
進程間資料是獨立的,可以藉助於隊列或管道實現通訊,二者都是基於訊息傳遞的 。雖然進程間資料獨立,但可以通過Manager實現資料共用,事實上Manager的功能遠不止於此。
from multiprocessing import Manager,Process,Lockdef work(d,lock): with lock: #不加鎖而操作共用的資料,肯定會出現資料錯亂 d[‘count‘]-=1if __name__ == ‘__main__‘: lock=Lock() with Manager() as m: dic=m.dict({‘count‘:100}) p_l=[] for i in range(100): p=Process(target=work,args=(dic,lock)) p_l.append(p) p.start() for p in p_l: p.join() print(dic)Manager
python------生產者消費者模型 和 管道