生产者消费者模型
1.什么是生产者消费者模型
- 生产者 : 程序中负责产生数据的一方
- 消费者 : 程序中负责处理数据的一方
2.为什么引入生产者消费者模型
在并发编程中, 生产者消费者模式通过一个容器来解决生产者和消费者之间的强耦合性, 两者之间不再是直接通信, 而是通过堵塞队列来进行通信, 生产者(生产速度快)不必再等待消费者是否处理完数据, 消费者直接从队列中取, 该队列就相当于一个缓冲区, 平衡了生产者和消费者的工作能力, 从而提高了程序整体的数据处理速度
3.如何实现
通过队列 : 生产者------>队列------->消费者
4.生产者消费者示例
from multiprocessing import Process, Queue
import time, random
def producer(q, name, food):
for i in range(3):
res = f"{food}{i}"
time.sleep(random.randint(1, 3)) # 模拟生产者数据产出时间
q.put(res) # 将产生的数据放入到队列中
print(f"\033[1;35m{name}:生产了:{res}\033[0m")
def consumer(q, name):
while True:
res = q.get() # 取出数据
if res == None: break # 判断是否None, None代表队列取完了,结束
time.sleep(random.randint(1, 3)) # 模拟消费者处理数据时间
print(f"\033[1;36m{name}吃了{res}\033[0m")
if __name__ == "__main__":
q = Queue() # 创建队列
# 开启三个生产者进程
p1 = Process(target=producer, args=(q, "shawn", "香肠"))
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()
c2.start()
# 等待生产者全部生产完毕结束进程
p1.join()
p2.join()
p3.join()
# 主进程再想队列里面放入两个None,当消费者拿到后代表取完了
q.put(None)
q.put(None)
print("痞老板:主")
'''输出
shawn:生产了:香肠0
派大星:生产了:热狗0
章鱼哥吃了香肠0
蟹老板吃了热狗0
派大星:生产了:热狗1
shawn:生产了:香肠1
海绵宝宝:生产了:鸡0
章鱼哥吃了热狗1
海绵宝宝:生产了:鸡1
派大星:生产了:热狗2
章鱼哥吃了鸡0
蟹老板吃了香肠1
shawn:生产了:香肠2
海绵宝宝:生产了:鸡2
痞老板:主
蟹老板吃了热狗2
章鱼哥吃了鸡1
蟹老板吃了香肠2
章鱼哥吃了鸡2
Process finished with exit code 0
'''
5.第二种生产者消费者模型使用JoinableQueue类 (了解)
- JoinableQueue类的实例
q = JoinableQueue([maxsize])
: 与 Queue 的对象一样, 但队列允许项目的使用者通知生成者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的
- 方法
方法 | 作用 |
---|---|
q.task_done( ) | 使用者使用此方法发出信号,表示q.get( )的返回项目已经被处理。如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常 |
q.join( ) | 生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。阻塞将持续到队列中的每个项目均调用q.task_done()方法为止 |
from multiprocessing import Process, JoinableQueue
import time, random
def producer(q, name, food):
for i in range(3):
res = f"{food}{i}"
q.put(res)
time.sleep(random.randint(1, 3))
print(f"\033[1;35m{name}:生产了:{res}\033[0m")
q.join() # 等待每个生产者自己放入的数据被消费者取完才结束该进程
def consumer(q, name):
while True:
res = q.get()
if res == None: break
time.sleep(random.randint(1, 3))
print(f"\033[1;36m{name}吃了{res}\033[0m")
q.task_done()
# 消费者每次取走一个数据都发送一个task_done信号,生产者那边的计数相应减1
if __name__ == "__main__":
q = JoinableQueue() # 创建一个对象
# 创建三个生产者
p1 = Process(target=producer, args=(q, "shawn", "香肠"))
p2 = Process(target=producer, args=(q, "派大星", "热狗"))
p3 = Process(target=producer, args=(q, "海绵宝宝", "鸡"))
# 创建两个消费者
c1 = Process(target=consumer, args=(q, "章鱼哥"))
c2 = Process(target=consumer, args=(q, "蟹老板"))
# 将两个消费者设置成守护进程, 主进程代码结束,这两个消费者进程相应结束
c1.daemon = True
c2.daemon = True
p1.start()
p2.start()
p3.start()
c1.start()
c2.start()
# 等待三个生产者进程结束
p1.join()
p2.join()
p3.join()
🔰#原理分析 : 生产者生产数据, 假设一个生产者生产3个数据带队列,每个相应的计数为3
🔰#消费者从队列中取走数据的时候发送task_done信号给生产者,生产者的计数3-1,剩下两个
🔰#消费者继续取数据并发送信号,当生产者的计数为0,代表队列已经取完了,这时q.join()就不再进行堵塞,生产者进程结束
🔰#而此时的消费者也已经没有作用了,将消费者进程设置成守护进程,主进程等待生产者进程结束就结束,消费者进程自然被带走
'''输出
shawn:生产了:香肠0
海绵宝宝:生产了:鸡0
章鱼哥吃了香肠0
派大星:生产了:热狗0
蟹老板吃了热狗0
shawn:生产了:香肠1
海绵宝宝:生产了:鸡1
章鱼哥吃了鸡0
蟹老板吃了香肠1
shawn:生产了:香肠2
派大星:生产了:热狗1
章鱼哥吃了鸡1
蟹老板吃了热狗1
海绵宝宝:生产了:鸡2
派大星:生产了:热狗2
章鱼哥吃了香肠2
蟹老板吃了鸡2
章鱼哥吃了热狗2
Process finished with exit code 0
'''
十三.信号量 Semaphore (了解)
互斥锁同时只允许一个线程修改数据, 而 Semaphore 允许同时有一定数量的进程更改数据, 就像理发店, 比如只有3个托尼老师, 那最多只允许3个人同时理发, 后面的人只能等到有人理完了才能开始, 如果指定信号量为3, 那么来一个人获得一把锁, 计数加1, 当计数等于3时, 后面的人均需要等待 , 一旦释放, 就有人可以获得一把锁
from multiprocessing import Semaphore,Process
import time,random
def haircut(sem,name):
start_time = time.time()
sem.acquire() # 加锁
print(f"{name}开始理发")
time.sleep(random.randint(2,3)) # 模拟理发时间
print(f"{name}理发加等待用时%.2f"%(time.time()-start_time))
sem.release() # 解锁
if __name__ == '__main__':
sem = Semaphore(3) # 最大进程数为3
user_list = []
for i in range(8):
p = Process(target=haircut,args=(sem,f"明星{i}"))
p.start()
user_list.append(p)
for obj in user_list:
obj.join()
print("关门")
'''输出
明星0开始理发
明星1开始理发
明星2开始理发
明星0理发加等待用时3.00
明星3开始理发
明星1理发加等待用时3.00
明星4开始理发
明星2理发加等待用时3.00
明星5开始理发
明星3理发加等待用时4.93
明星6开始理发
明星4理发加等待用时4.87
明星7开始理发
明星5理发加等待用时5.82
明星7理发加等待用时6.69
明星6理发加等待用时7.74
关门
Process finished with exit code 0
'''