生产者与消费者模型
在并发编程中,如果生产者处理速度很快,而消费者处理速度比较慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个等待的问题,就引入了生产者与消费者模型。让它们之间可以不停的生产和消费
什么是生产者消费者模式
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
这就像,在餐厅,厨师做好菜,不需要直接和客户交流,而是交给前台,而客户去饭菜也不需要不找厨师,直接去前台领取即可。
例如:
from queue import Queue
from threading import Thread
import time
q=Queue(maxsize=10)
#生产者
def cooker(name):
count=1
while True:
q.put("包子%d"%count)
print('%s生产了包子%d'%(name,count))
count+=1
time.sleep(0.5)
#消费者
def consumer(name):
while True:
print("%s吃了%s"%(name,q.get()))
time.sleep(1)
if __name__ == '__main__':
#厨师1
c1=Thread(target=cooker,args=(("程楚师",)))
c1.start()
# 厨师2
c2 = Thread(target=cooker, args=(("李楚师",)))
c2.start()
#消费者
g1=Thread(target=consumer,args=("小明",))
g2=Thread(target=consumer,args=("小红 ",))
g2.start()
进程之间通讯
进程之间是独立的,如果我想两个进程之间进行通讯怎么办呢?我们可以使用Queue 队列,队列是一种先进先出的存储数据结构,就比如排队上厕所一个道理。
两个进程通讯,就是一个子进程往queue中写内容,另一个进程从queue中取出数据。就实现了进程间的通讯了。
一: queue队列
- 创建 queue队列对象
q = multiprocessing.Queue(3) # 3表示只能存放3个数据
参数 :maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。
返回值q 是队列对象 - put()方法 ,向队列中存放数据。如果队列已满,此方法将阻塞至有空间可用为止。
- get()返回q中的一个项目。如果q为空,此方法将阻塞,直到队列中有项目可用为止。
- get_nowait(): 不等待,直接抛出异常
- full()如果q已满,返回为True
- q.empty() 如果调用此方法时 q为空,返回True。
例如:
from multiprocessing import Queue
q=Queue(3)
#格式:put(self, obj, block=True, timeout=None)
#参数:block,指阻塞,默认为真,表示如果队列已满,程序已满,
##如果为False,表示不需要阻塞,如果队列已满,将会派出异常
#timeout:表示阻塞时间,单位为秒。
q.put(1)
q.put(2)
q.put(3)
# q.put(4)#block指阻塞,由于默认阻塞状态,4程序将会阻塞在这里不运行
try:
# q.put(4,block=False)
q.put(4, block=True,timeout=2)#如果阻塞,等两秒才会运行
except:
print("消息队列已满,现有消息数量:%s"%(q.qsize()))
print("`````````````")
#
#put的另一种形式
#q.put_nowait()强塞,等价于q.put(5,block=False)
try:
q.put_nowait(5)
except:
print("消息队列已满,现有消息数量:%s" % (q.qsize()))
#推荐方式:先判断消息队列是否已满,再放对象
if not q.full():
q.put(6)
#``````````````````````````````````````````````````````````
#----------取---------------------------
#格式:get(block=True,timeout=None)
q.get()
q.get()
q.get()
print("当前队列的状态:%s"%(q.qsize()))
#推荐方式:先判断消息队列是否为空,再取对象
if not q.empty():
q.get() #如果队列为空,一直阻塞
print("``````````````````````````")
#取的另一种写法
q.get_nowait()#强取,等价于q.get(block=False)
**进程池**
当需要创建的子进程数量不多时,我们可以直接利用multiporcessing中的Process动态生成多个进程,但是如果现在有100个任务需要处理,那我们需要多少个子进程呢,如果我们创建100个子进程也可以实现,但是资源比较浪费。我们也可以创建指定个数个子进程,例如只创建10个子进程,让着10个子进程重复的执行任务,这样就节约了资源。
就比如我们去景区湖上游玩,游船是重复利用的。
我们可以使用multiprocessing模块提供的Pool类,也就是进程池,可以到达进程重复利用。泸沽岛
创建进程池对象的时候可以指定一个最大进程数,当有新的请求提交到进程池中,如果池中的进程数还没有满,那么就会创建一个新的进程用来执行该请求,但是如果池中的进程数满了,该请求就会等待,知道进程池中的进程有结束的了,才会使用这个结束的进程来执行新的任务。
join 主进程等待所有子进程执行完毕,必须在close之后。
close 等待所有进程结束才关闭线程池
#进程池
from multiprocessing import Pool
import time,random
def worker(msg):
t_start=time.time()
print("%s---开始执行"%(msg))
time.sleep(random.random())#模拟工作所消耗的时间
t_end=time.time()
print("%s--执行完毕,消耗时间为:%.2f"%(msg,t_end-t_start))
if __name__ == '__main__':
p=Pool(3)#创建一个容量是3 的进程池
for i in range(10):
#将进程添加到进程池使用的是apply_async
#格式为:进程池,apply_async(func=单个进程要调用的名,args=(参数1,参数2.。。)
#每次循环将会用空闲的子进程去调用目标
p.apply_async(func=worker,args=(i,))#并行(异步)操作
# p.apply_async(func=worker,args=(i,))#串行操作
#关闭进程池,关闭后,进程池不在接收新的任务
print("----------------start-----------------")
p.close()
#等待进程池中的所有子进程都结束后,注意:p.join一定要放在close之后
p.join()
print("-----------------end--------------------")
**`线程队列Queue`**
队列是一种先进先出(FIFO)的存储数据结构,就比如排队上厕所一个道理。
1.创建一个“队列”对象
import Queue # 导入模块
q = Queue.Queue(maxsize = 10)
Queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。
2.将一个值放入队列中 q.put(10)
调用队列对象的put()方法在队尾插入一个项目。
3. 将一个值从队列中取出q.get()
从队头删除并返回一个项目。如果取不到数据则一直等待。
4.q.qsize() 返回队列的大小
5.q.empty() 如果队列为空,返回True,反之False
6.q.full() 如果队列满了,返回True,反之False
7.q.put_nowait(item) ,如果取不到不等待,之间抛出异常。
8.q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号
9.q.join() 收到q.task_done()信号后再往下执行,否则一直等待。或者最开始时没有放数据join()不会阻塞。
q.task_done() 和 q.join() 通常一起使用。
from queue import Queue#先进先出队列
from queue import PriorityQueue#继承了先进先出队列
from queue import LifoQueue#后进先出
#创建一个线程队列
q=Queue(3)
#放数据
q.put("q1")
q.put("q2")
q.put("q3")
print(q.full())
print(q.empty())
#取数据
q.get()#q1
q.get()#q2
q.get()#q3
print(q.empty())#True
#队列的优先级
pq=PriorityQueue()
#任务不要直接put,需要封装到元祖当中
#格式:(数字,消息),数字越小,优先级越高,
pq.put((1,'King',))
pq.put((0,'Quen',))
pq.put((2,'Jack',))
pq.put((-1,'people',))
print(pq.get())#-1的消息,先输出
i=0
while i<pq.qsize():
print(pq.get())
"""
(-1, 'people')
(0, 'Quen')
(1, 'King')
(2, 'Jack')
"""
#后进先出队列
lq=LifoQueue()
lq.put("d1")
lq.put("d2")
lq.put("d3")
print(lq.get())
print(lq.get())
print(lq.get())
#需求
#在父进程当中创建两个子线程,一个往 Queue里写数据,一个从 Queue里读取数据
from threading import Thread
from queue import Queue
import time,random
#写操作
def write(q):
for value in [‘a’,‘b’,‘c’,‘d’,“exit”]:#队列最后一个为退出标志
print(“Put %s from Queue”%(value))
q.put(value)
time.sleep(random.random())
#读操作
def read(q):
print(“我要read啦”)
while True:
if not q.empty():
value=q.get()
if value==‘exit’:#使用get一定注意,在这里用q.get()的话会取用两次,
break #-造成丢失,这是因为队列是以此取值,所以每次取值都不同
else:
print(“Get %s from Queue”%(value))
time.sleep(random.random())
if name == ‘main’:
#创建一个队列
q=Queue()
#创建两个线程
pw=Thread(target=write,args=(q,))
pr=Thread(target=read,args=(q,))
#启动
pw.start()
pr.start()
pw.join() # 此时pw进程结束
print("所有读写进程结束")