一、为什么要使用生产者和消费者?

  在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程,在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据,同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者,为了解决这个问题于是引入了生产者和消费者模式。

二、什么是生产者消费者模式

  生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

  1. from multiprocessing import Process,Queue
  2. import time,random,os
  3. def consumer(q):
  4. while True:
  5. res=q.get()
  6. if res is None:break #收到结束信号则结束
  7. time.sleep(random.randint(1,3))
  8. print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
  9.  
  10. def producer(q):
  11. for i in range(10):
  12. time.sleep(random.randint(1,3))
  13. res='包子%s' %i
  14. q.put(res)
  15. print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
  16. q.put(None) #发送结束信号
  17. if __name__ == '__main__':
  18. q=Queue()
  19. #生产者们:即厨师们
  20. p1=Process(target=producer,args=(q,))
  21.  
  22. #消费者们:即吃货们
  23. c1=Process(target=consumer,args=(q,))
  24.  
  25. #开始
  26. p1.start()
  27. c1.start()
  28. print('主')

基于队列实现生产者消费者模型

注意:结束信号None,不一定要由生产者发,主进程里同样可以发,但主进程需要等生产者结束后才应该发送该信号

  1. from multiprocessing import Process,Queue
  2. import time,random,os
  3. def consumer(q):
  4. while True:
  5. res=q.get()
  6. if res is None:break #收到结束信号则结束
  7. time.sleep(random.randint(1,3))
  8. print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
  9.  
  10. def producer(q):
  11. for i in range(2):
  12. time.sleep(random.randint(1,3))
  13. res='包子%s' %i
  14. q.put(res)
  15. print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
  16.  
  17. if __name__ == '__main__':
  18. q=Queue()
  19. #生产者们:即厨师们
  20. p1=Process(target=producer,args=(q,))
  21.  
  22. #消费者们:即吃货们
  23. c1=Process(target=consumer,args=(q,))
  24.  
  25. #开始
  26. p1.start()
  27. c1.start()
  28.  
  29. p1.join()
  30. q.put(None) #发送结束信号
  31. print('主')

主进程在生产者生产完毕后发送结束信号None

  1. from multiprocessing import Process,Queue
  2. import time,random,os
  3. def consumer(q):
  4. while True:
  5. res=q.get()
  6. if res is None:break #收到结束信号则结束
  7. time.sleep(random.randint(1,3))
  8. print('\033[45m%s 吃 %s\033[0m' %(os.getpid(),res))
  9.  
  10. def producer(name,q):
  11. for i in range(2):
  12. time.sleep(random.randint(1,3))
  13. res='%s%s' %(name,i)
  14. q.put(res)
  15. print('\033[44m%s 生产了 %s\033[0m' %(os.getpid(),res))
  16.  
  17. if __name__ == '__main__':
  18. q=Queue()
  19. #生产者们:即厨师们
  20. p1=Process(target=producer,args=('包子',q))
  21. p2=Process(target=producer,args=('骨头',q))
  22. p3=Process(target=producer,args=('泔水',q))
  23.  
  24. #消费者们:即吃货们
  25. c1=Process(target=consumer,args=(q,))
  26. c2=Process(target=consumer,args=(q,))
  27.  
  28. #开始
  29. p1.start()
  30. p2.start()
  31. p3.start()
  32. c1.start()
  33.  
  34. p1.join() #必须保证生产者全部生产完毕,才应该发送结束信号
  35. p2.join()
  36. p3.join()
  37. q.put(None) #有几个消费者就应该发送几次结束信号None
  38. q.put(None) #发送结束信号
  39. 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异常。

应该特别注意管道端点的正确管理问题:如果生产者或者消费者中都没有使用管道的某个端点,就应将它关闭。这也说明了为何在生产者中关闭了管道的输出端,在消费者中关闭了管道的输入端。

  1. from multiprocessing import Process,Pipe
  2.  
  3. def consumer(p,name):
  4. produce, consume=p
  5. produce.close()
  6. while True:
  7. try:
  8. baozi=consume.recv()
  9. print('%s 收到包子:%s' %(name,baozi))
  10. except EOFError:
  11. break
  12.  
  13. def producer(seq,p):
  14. produce, consume=p
  15. consume.close()
  16. for i in seq:
  17. produce.send(i)
  18.  
  19. if __name__ == '__main__':
  20. produce,consume=Pipe()
  21.  
  22. c1=Process(target=consumer,args=((produce,consume),'c1'))
  23. c1.start()
  24.  
  25. seq=(i for i in range(10))
  26. producer(seq,(produce,consume))
  27.  
  28. produce.close()
  29. consume.close()
  30.  
  31. c1.join()
  32. print('主进程')

pipe实现生产者消费者模型

  1. from multiprocessing import Process,Pipe,Lock
  2.  
  3. def consumer(p,name,lock):
  4. produce, consume=p
  5. produce.close()
  6. while True:
  7. lock.acquire()
  8. baozi=consume.recv()
  9. lock.release()
  10. if baozi:
  11. print('%s 收到包子:%s' %(name,baozi))
  12. else:
  13. consume.close()
  14. break
  15.  
  16. def producer(p,n):
  17. produce, consume=p
  18. consume.close()
  19. for i in range(n):
  20. produce.send(i)
  21. produce.send(None)
  22. produce.send(None)
  23. produce.close()
  24.  
  25. if __name__ == '__main__':
  26. produce,consume=Pipe()
  27. lock = Lock()
  28. c1=Process(target=consumer,args=((produce,consume),'c1',lock))
  29. c2=Process(target=consumer,args=((produce,consume),'c2',lock))
  30. p1=Process(target=producer,args=((produce,consume),10))
  31. c1.start()
  32. c2.start()
  33. p1.start()
  34.  
  35. produce.close()
  36. consume.close()
  37.  
  38. c1.join()
  39. c2.join()
  40. p1.join()
  41. print('主进程')

多个消费之之间的竞争问题带来的数据不安全问题

四、数据之间的数据共享

  通过使用线程,推荐做法也是将程序设计为大量独立的线程集合,通过消息队列交换数据。这样极大地减少了对使用锁定和其他同步手段的需求,还可以扩展到分布式系统中。但进程间应该尽量避免通信,即使需要通信,也应该选择进程安全的工具来避免加锁带来的问题。

  进程间数据是独立的,可以借助于队列或管道实现通信,二者都是基于消息传递的 。虽然进程间数据独立,但可以通过Manager实现数据共享,事实上Manager的功能远不止于此。

  1. from multiprocessing import Manager,Process,Lock
  2. def work(d,lock):
  3. with lock: #不加锁而操作共享的数据,肯定会出现数据错乱
  4. d['count']-=1
  5.  
  6. if __name__ == '__main__':
  7. lock=Lock()
  8. with Manager() as m:
  9. dic=m.dict({'count':100})
  10. p_l=[]
  11. for i in range(100):
  12. p=Process(target=work,args=(dic,lock))
  13. p_l.append(p)
  14. p.start()
  15. for p in p_l:
  16. p.join()
  17. print(dic)

Manager

  1.  

python------生产者消费者模型 和 管道的更多相关文章

  1. Python_生产者消费者模型、管道、数据共享、进程池

    1.生产者消费者模型 生产者 —— 生产数据的人 消费者 —— 消费数据的人 生产者消费者模型:供销数据不平衡的现象. import time import random from multiproc ...

  2. python生产者消费者模型

    业界用的比较广泛,多线程之间进行同步数据的方法,解决线程之间堵塞,互相不影响. server --> 生产者 client --> 消费者 在一个程序中实现又有生产者又有消费者 ,生产者不 ...

  3. python生产者消费者模型优点

    生产者消费者模型:解耦,通过队列降低耦合,支持并发,生产者和消费者是两个独立的并发体,他们之间使用缓存区作为桥梁连接,生产者指望里丢数据,就可以生产下一个数据了,消费者从中拿数据,这样就不会阻塞,影响 ...

  4. 操作系统OS,Python - 生产者消费者模型

    1. 缓冲区(此处用阻塞队列充当),解决消费者和生产者强耦合问题.(生产者和消费者不直接通信) 2. 通过平衡生产者线程和消费者线程,来提高程序整体处理数据速度. 3. 在并发编程中该模式能解决大多数 ...

  5. python 生产者消费者模型

    import time def consumer(name): print("%s开始吃包子了"%name) while True: ret = yield time.sleep( ...

  6. python并发编程之多进程(二):互斥锁(同步锁)&进程其他属性&进程间通信(queue)&生产者消费者模型

    一,互斥锁,同步锁 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的, 竞争带来的结果就是错乱,如何控制,就是加锁处理 part1:多个进程共享同一打印终 ...

  7. Python之路(第三十八篇) 并发编程:进程同步锁/互斥锁、信号量、事件、队列、生产者消费者模型

    一.进程锁(同步锁/互斥锁) 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的, 而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理. 例 ...

  8. 进程同步控制(锁,信号量,事件), 进程通讯(队列和管道,生产者消费者模型) 数据共享(进程池和mutiprocess.Pool模块)

    参考博客 https://www.cnblogs.com/xiao987334176/p/9025072.html#autoid-1-1-0 进程同步(multiprocess.Lock.Semaph ...

  9. python 全栈开发,Day39(进程同步控制(锁,信号量,事件),进程间通信(队列,生产者消费者模型))

    昨日内容回顾 python中启动子进程并发编程并发 :多段程序看起来是同时运行的ftp 网盘不支持并发socketserver 多进程 并发异步 两个进程 分别做不同的事情 创建新进程join :阻塞 ...

  10. python网络编程--进程(方法和通信),锁, 队列,生产者消费者模型

    1.进程 正在进行的一个过程或者说一个任务.负责执行任务的是cpu 进程(Process: 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础.在 ...

随机推荐

  1. web项目分层设计

    model.dao.service.controller之间的关系,还有util和task的简介 model: 与数据库中的表一一对应,实现set和get的方法.

  2. OC + RAC (八) 查看信号状态和跳过信号

    -(void)_test9{ /// RACCommand又叫命令 是用来收发数据的 监听按钮点击,网络请求.... RACCommand * command = [[RACCommand alloc ...

  3. web高拍仪图片上传

    公司引进高拍仪,想拍完照片点上传按钮直接上传图片.高拍仪接口能提供照片的本地路径,现在的问题是不用file控件选择,只有路径,不知道如何上传到服务器,求解决方案. 方法: 使用泽优Web图片上传控件( ...

  4. php选择文件夹上传

    最近遇见一个需要上传百兆大文件的需求,调研了七牛和腾讯云的切片分段上传功能,因此在此整理前端大文件上传相关功能的实现. 在某些业务中,大文件上传是一个比较重要的交互场景,如上传入库比较大的Excel表 ...

  5. [14th CSMO Day 1 <平面几何>]

    关于LowBee苦思冥想的结果(仅供参考):

  6. (3)狄泰软件学院C++课程学习剖析一

    深度剖析C++第一部分 1.类是一种模型,这种模型可以创建出一个对应的实体.有了类不一定有对应的实体,但是一个实体必定属于某一个类. 2.类用于抽象的描述 一类事物所持有的属性和行为:对象是具体的事物 ...

  7. RabbitMQ生产者消费者模型构建(三)

    ConnectionFactory:获取连接(地址,端口号,用户名,密码,虚拟主机等) Connection:一个连接 Channel:数据通信信道,可发送.接收消息 Queue:具体的消息存储队列 ...

  8. LintCode之合并排序数组II

    题目描述: 分析:题目的意思是把数组A和数组B合并到数组A中,且数组A有足够的空间容纳A和B的元素,合并后的数组依然是有序的. 我的代码: public class Solution { /* * @ ...

  9. Linux 用户和组信息

    linux系统是一个多用户多任务的分时操作系统,任何一个要使用系统资源的用户,都必须首先向系统管理员申请一个账号.在使用linux操作系统时候,通常我们会遇到对多用户进行管理.比如: 用户账号的添加. ...

  10. EZOJ #361地理

    分析 就是分别维护l和r的个数 然后对于询问区间[L,R] 之后l树状数组中小于等于R的个数减掉r树状数组中小于L的即可 代码 #include<bits/stdc++.h> using ...