目录:
- 异常处理
- python进程
- python并发之多进程
一、异常处理(try...except...)
1、程序中难免出现错误,而错误分成两种:
a.语法错误:
b.逻辑错误(逻辑错误)
2、异常定义:异常就是程序运行时发生错误的信号。
在python中,错误触发的异常,是以异常追踪信息、 异常类型、异常值三部分组成,如下:
执行的结果为:
#异常追踪信息
Traceback:
Traceback (most recent call last):
File "D:/python/day29/aa.py", line 5, in <module>
nulige
# 异常类型:异常值
NameError: name 'nulige' is not defined
3、异常的种类:
a.常用分类
b.更多分类
4、异常处理定义:
python解释器检测到错误,触发异常(也允许程序员自己触发异常)
程序员编写特定的代码,专门用来捕捉这个异常(这段代码与程序逻辑无关,与异常处理有关)
如果捕捉成功则进入另外一个处理分支,执行你为其定制的逻辑,使程序不会崩溃,这就是异常处理
5、异常处理的应用场景:
python解析器去执行程序,检测到了一个错误时,触发异常,异常触发后且没被处理的情况下,程序就在当前异常处终止,后面的代码不会运行,谁会去用一个运行着突然就崩溃的软件。所以你必须提供一种异常处理机制来增强你程序的健壮性与容错性 。
6、异常处理的使用
首先须知,异常是由程序的错误引起的,语法上的错误跟异常处理无关,必须在程序运行前就修正
a.使用if判断方式:
if判断式的异常处理只能针对某一段代码,对于不同的代码段的相同类型的错误你需要写重复的if来进行处理。
在你的程序中频繁的写与程序本身无关,与异常处理有关的if,可读性极其的差
这是可以解决异常的,只是存在上述两个的问题,所以,千万不要妄下定论if不能用来异常处理。
b.python为每一种异常定制了一个类型,然后提供了一种特定的语法结构用来进行异常处理
1)基本语法:
try: #异常捕捉
pass # 被检测的代码块
except <异常类型> as <e>:
pass # 一旦try检测到异常,就执行这个位置的逻辑
<except ...: > # 异常处理的多分枝
<else:>
pass # 报错执行逻辑
finally:
pass # 最后不管报不报错都执行的逻辑(为状态逻辑)
2)异常类只能用来处理指定的异常情况,如果非指定异常则无法处理。
3)多分支
4)万能异常:python万能异常,Exception,他可以捕获任意异常
5)主动触发异常
6)自定义异常
7)断言
7、try...except的方式与if的比较:
try..except这种异常处理机制就是取代if那种方式,让你的程序在不牺牲可读性的前提下增强健壮性和容错性。异常处理中为每一个异常定制了异常类型(python中统一了类与类型,类型即类),对于同一种异常,一个except就可以捕捉到,可以同时处理多段代码的异常(无需‘写多个if判断式’)减少了代码,增强了可读性。
使用try..except的方式
a.把错误处理和真正的工作分开来
b.代码更易组织,更清晰,复杂的工作任务更容易实现;
c.毫无疑问,更安全了,不至于由于一些小的疏忽而使程序意外崩溃了;
8、使用异常处理注意事项。
try...except应该尽量少用,因为它本身就是你附加给你的程序的一种异常处理的逻辑,与你的主要的工作是没有关系的这种东西加的多了,会导致你的代码可读性变差。 异常处理只有在有些异常无法预知的情况下,才应该加上try...except,其他的逻辑错误应该尽量修正。
9、总结:
1)try..except的方式,只是python提供给你一种特定的语法结构去做这件事,对于不同代码的同一种异常,python为你定制了一中类型,一个expect就可以捕捉到。
2)try...except应该尽量少用,因为它本身就是你附加给你的程序的一种异常处理的逻辑,与你的主要的工作是没有关系的这种东西加的多了,会导致你的代码可读性变差。异常处理只有在有些异常无法预知的情况下,才应该加上try...except,其他的逻辑错误应该尽量修正。
二、python进程(multiprocessing)
1、操作系统哦你的概念:
操作系统位于底层硬件与应用软件之间的一层。工作方式:向下管理硬件,向上提供接口。
操作系统进行进程切换:1.出现IO操作;2.固定时间。
固定时间很短,人感受不到。每一个应用层运行起来的程序都是进程。
2、进程的概念:
程序仅仅只是一堆代码而已,而进程指的是程序的运行过程。需要强调的是:同一个程序执行两次,那也是两个进程。
进程:资源管理单位(容器)。
线程:最小执行单位,管理线程的是进程。
进程定义:
进程就是一个程序在一个数据集上的一次动态执行过程。进程一般由程序、数据集、进程控制块三部分组成。我们编写的程序
用来描述进程要完成哪些功能以及如何完成;数据集则是程序在执行过程中所需要使用的资源;进程控制块用来记录进程的外
部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。
3、进程与线程的关系:
在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程。
多线程(即多个控制线程)的概念是,在一个进程中存在多个控制线程,控制该进程的地址空间。
进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。
进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。或者说
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
线程则是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。
4、并行和并发:
无论是并行还是并发,在用户看来都是'同时'运行的,而一个cpu同一时刻只能执行一个任务。
并行:同时运行,只有具备多个cpu才能实现并行。
并发:是伪并行,即看起来是同时运行,单个cpu+多道技术。
ps.多道技术:
内存中同时存入多道(多个)程序,cpu从一个进程快速切换到另外一个,使每个进程各自运行几十或几百毫秒,
这样,虽然在某一个瞬间,一个cpu只能执行一个任务,但在1秒内,cpu却可以运行多个进程,这就给人产生了并行的错觉,
即伪并发,以此来区分多处理器操作系统的真正硬件并行(多个cpu共享同一个物理内存)。
5、同步与异步
同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;
异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。
举个例子,打电话时就是同步通信,发短息时就是异步通信。
6、进程的创建
但凡是硬件,都需要有操作系统去管理,只要有操作系统,就有进程的概念,就需要有创建进程的方式,一些操作系统只为一个应用程序设计,比如微波炉中的控制器,一旦启动微波炉,所有的进程都已经存在。
而对于通用系统(跑很多应用程序),需要有系统运行过程中创建或撤销进程的能力,主要分为4中形式创建新的进程
1. 系统初始化(查看进程linux中用ps命令,windows中用任务管理器,前台进程负责与用户交互,后台运行的进程与用户无关,运行在后台并且只在需要时才唤醒的进程,称为守护进程,如电子邮件、web页面、新闻、打印)
2. 一个进程在运行过程中开启了子进程(如nginx开启多进程,os.fork,subprocess.Popen等)
3. 用户的交互式请求,而创建一个新进程(如用户双击暴风影音)
4. 一个批处理作业的初始化(只在大型机的批处理系统中应用)
无论哪一种,新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的:
1. 在UNIX中该系统调用是:fork,fork会创建一个与父进程一模一样的副本,二者有相同的存储映像、同样的环境字符串和同样的打开文件(在shell解释器进程中,执行一个命令就会创建一个子进程)
2. 在windows中该系统调用是:CreateProcess,CreateProcess既处理进程的创建,也负责把正确的程序装入新进程。
关于创建的子进程,UNIX和windows
1.相同的是:进程创建后,父进程和子进程有各自不同的地址空间,任何一个进程的在其地址空间中的修改都不会影响到另外一个进程。
2.不同的是:在UNIX中,子进程的初始地址空间是父进程的一个副本,提示:子进程和父进程是可以有只读的共享内存区的。但是对于windows系统来说,从一开始父进程与子进程的地址空间就是不同的。
7、进程的终止
1. 正常退出(自愿,如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出,在linux中用exit,在windows中用ExitProcess)
2. 出错退出(自愿,程序员主动抛出异常,例如raise)
3. 严重错误(非自愿,执行非法指令,如引用不存在的内存,1/0等)
4. 被其他进程杀死(非自愿,如kill -9)
8、进程的层次结构
无论UNIX还是windows,进程只有一个父进程,不同的是:
1. 在UNIX中所有的进程,都是以init进程为根,组成树形结构。父子进程共同组成一个进程组,这样,当从键盘发出一个信号时,该信号被送给当前与键盘相关的进程组中的所有成员。
2. 在windows中,没有进程层次的概念,所有的进程都是地位相同的,唯一类似于进程层次的暗示,是在创建进程时,父进程得到一个特别的令牌(称为句柄),该句柄可以用来控制子进程,但是父进程有权把该句柄传给其他子进程,这样就没有层次了。
9、进程的状态
tail -f access.log |grep '404'
执行程序tail,开启一个子进程,执行程序grep,开启另外一个子进程,两个进程之间基于管道'|'通讯,将tail的结果作为grep的输入。
进程grep在等待输入(即I/O)时的状态称为阻塞,此时grep命令都无法运行
其实在两种情况下会导致一个进程在逻辑上不能运行,
1. 进程挂起是自身原因,遇到I/O阻塞,便要让出CPU让其他进程去执行,这样保证CPU一直在工作
2. 与进程无关,是操作系统层面,可能会因为一个进程占用时间过多,或者优先级等原因,而调用其他的进程去使用CPU
因而一个进程由三种状态
10、进程并发的实现
进程并发的实现在于,硬件中断一个正在运行的进程,把此时进程运行的所有状态保存下来,为此,操作系统维护一张表格,即进程表(process table),每个进程占用一个进程表项(这些表项也称为进程控制块)
该表存放了进程状态的重要信息:程序计数器、堆栈指针、内存分配状况、所有打开文件的状态、帐号和调度信息,以及其他在进程由运行态转为就绪态或阻塞态时,必须保存的信息,从而保证该进程在再次启动时,就像从未被中断过一样
三、python并发之多进程(multiprocessing模块)
1、multiprocessing模块介绍:
python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。Python提供了非常好用的多进程包multiprocessing。
multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。
multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。
需要再次强调的一点是:与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内。
2、Process类的介绍:
Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)
强调:
需要使用关键字的方式来指定参数,args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号
参数介绍:
1)group参数未使用,值始终为None
2)target表示调用对象,即子进程要执行的任务
3)args表示调用对象的位置参数元组,args=(1,2,'egon',)
4)kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}
5)name为子进程的名称
方法介绍:
1)p.start():启动进程,并调用该子进程中的p.run()
2)p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
3)p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
4)p.is_alive():如果p仍然运行,返回True
5)p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程
属性介绍:
1)p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
2)p.name:进程的名称
3)p.pid:进程的pid
4)p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
5)p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)
3、Process类的使用
1.创建并开启子进程的两种方式
ps.注意:在windows中Process()必须放到# if __name__ == '__main__':下
由于Windows没有fork,多处理模块启动一个新的Python进程并导入调用模块。 如果在导入时调用Process(),那么这将启动无限继承的新进程(或直到机器耗尽资源)。 这是隐藏对Process()内部调用的原,使用if __name__ == “__main __”,这个if语句中的语句将不会在导入时被调用。
a.方法一:函数方法调用
1 #开进程的方法一: 2 import time 3 import random 4 from multiprocessing import Process 5 def piao(name): 6 print('%s piaoing' %name) 7 time.sleep(random.randrange(1,5)) 8 print('%s piao end' %name) 9 10 11 12 p1=Process(target=piao,args=('egon',)) #必须加,号 13 p2=Process(target=piao,args=('alex',)) 14 p3=Process(target=piao,args=('wupeqi',)) 15 p4=Process(target=piao,args=('yuanhao',)) 16 17 p1.start() 18 p2.start() 19 p3.start() 20 p4.start() 21 print('主线程')
b. 方法二:类定义式调用
1 #开进程的方法二: 2 import time 3 import random 4 from multiprocessing import Process 5 6 7 class Piao(Process): 8 def __init__(self,name): 9 super().__init__() 10 self.name=name 11 def run(self): 12 print('%s piaoing' %self.name) 13 14 time.sleep(random.randrange(1,5)) 15 print('%s piao end' %self.name) 16 17 p1=Piao('p1') 18 p2=Piao('p2') 19 p3=Piao('p3') 20 p4=Piao('p4') 21 22 p1.start() #start会自动调用run 23 p2.start() 24 p3.start() 25 p4.start() 26 print('主线程')
c. socket结合process使用
1 from socket import * 2 from multiprocessing import Process 3 4 server=socket(AF_INET,SOCK_STREAM) 5 server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) 6 server.bind(('127.0.0.1',8080)) 7 server.listen(5) 8 9 def talk(conn,client_addr): 10 while True: 11 try: 12 msg=conn.recv(1024) 13 if not msg:break 14 conn.send(msg.upper()) 15 except Exception: 16 break 17 18 if __name__ == '__main__': #windows下start进程一定要写到这下面 19 while True: 20 conn,client_addr=server.accept() 21 p=Process(target=talk,args=(conn,client_addr)) 22 p.start()
1 from socket import * 2 3 client=socket(AF_INET,SOCK_STREAM) 4 client.connect(('127.0.0.1',8080)) 5 6 7 while True: 8 msg=input('>>: ').strip() 9 if not msg:continue 10 11 client.send(msg.encode('utf-8')) 12 msg=client.recv(1024) 13 print(msg.decode('utf-8'))
ps.每来一个客户端,都在服务端开启一个进程,如果并发来一个万个客户端,要开启一万个进程吗,你自己尝试着在你自己的机器上开启一万个,10万个进程试一试。
解决方法:进程池
2.Process对象的其他方法或属性
a.进程对象的其他方法一:terminate,is_alive
terminate()# 关闭进程,不会立即关闭,所以is_alive立刻查看的结果可能还是存活
is_alive() # 检查子进程是否存活True/False
1 #进程对象的其他方法一:terminate,is_alive 2 from multiprocessing import Process 3 import time 4 import random 5 6 class Piao(Process): 7 def __init__(self,name): 8 self.name=name 9 super().__init__() 10 11 def run(self): 12 print('%s is piaoing' %self.name) 13 time.sleep(random.randrange(1,5)) 14 print('%s is piao end' %self.name) 15 16 17 p1=Piao('egon1') 18 p1.start() 19 20 p1.terminate()#关闭进程,不会立即关闭,所以is_alive立刻查看的结果可能还是存活 21 print(p1.is_alive()) #结果为True 22 23 print('开始') 24 print(p1.is_alive()) #结果为False
b.进程对象的其他方法二:p.daemon=True,p.join
daemon() #默认为False,可以为True 一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程死,p跟着一起死
join()#等待子进程停止,在执行主进程,等0.0001秒就不再等了
1 #进程对象的其他方法二:p.daemon=True,p.join 2 from multiprocessing import Process 3 import time 4 import random 5 6 class Piao(Process): 7 def __init__(self,name): 8 self.name=name 9 super().__init__() 10 def run(self): 11 print('%s is piaoing' %self.name) 12 time.sleep(random.randrange(1,3)) 13 print('%s is piao end' %self.name) 14 15 16 p=Piao('egon') 17 p.daemon=True #一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程死,p跟着一起死 18 p.start() 19 p.join(0.0001) #等待p停止,等0.0001秒就不再等了 20 print('开始')
1 from multiprocessing import Process 2 3 import time 4 import random 5 def piao(name): 6 print('%s is piaoing' %name) 7 time.sleep(random.randint(1,3)) 8 print('%s is piao end' %name) 9 10 p1=Process(target=piao,args=('egon',)) 11 p2=Process(target=piao,args=('alex',)) 12 p3=Process(target=piao,args=('yuanhao',)) 13 p4=Process(target=piao,args=('wupeiqi',)) 14 15 p1.start() 16 p2.start() 17 p3.start() 18 p4.start() 19 20 #注意:进程只要start就会在开始运行了,所以p1-p4.start()时,系统中已经有四个并发的进程了 21 #而我们p1.join()是在等p1结束,没错p1只要不结束主线程就会一直卡在原地,这也是问题的关键 22 #join是让主线程等,而p1-p4仍然是并发执行的,p1.join的时候,其余p2,p3,p4仍然在运行,等p1.join 23 #结束,可能p2,p3,p4早已经结束了,这样p2.join,p3.join.p4.join直接通过 24 # 所以4个join花费的总时间仍然是耗费时间最长的那个进程运行的时间 25 p1.join() 26 p2.join() 27 p3.join() 28 p4.join() 29 30 print('主线程') 31 32 33 #上述启动进程与join进程可以简写为 34 # p_l=[p1,p2,p3,p4] 35 # 36 # for p in p_l: 37 # p.start() 38 # 39 # for p in p_l: 40 # p.join()
c.进程对象的其他属性:name,pid
1 #进程对象的其他属性:name,pid 2 from multiprocessing import Process 3 import time 4 import random 5 class Piao(Process): 6 def __init__(self,name): 7 # self.name=name 8 # super().__init__() #Process的__init__方法会执行self.name=Piao-1, 9 # #所以加到这里,会覆盖我们的self.name=name 10 11 #为我们开启的进程设置名字的做法 12 super().__init__() 13 self.name=name 14 15 def run(self): 16 print('%s is piaoing' %self.name) 17 time.sleep(random.randrange(1,3)) 18 print('%s is piao end' %self.name) 19 20 p=Piao('p1') 21 p.start() 22 print('开始') 23 print(p.pid) #查看pid
d.进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的。
1 #多进程共享一个打印终端(用python2测试看两个进程同时往一个终端打印,出现打印到一行的错误) 2 from multiprocessing import Process 3 import time 4 class Logger(Process): 5 def __init__(self): 6 super(Logger,self).__init__() 7 def run(self): 8 print(self.name) 9 10 11 for i in range(1000000): 12 l=Logger() 13 l.start()
1 #多进程共享一套文件系统 2 from multiprocessing import Process 3 import time,random 4 5 def work(f,msg): 6 f.write(msg) 7 f.flush() 8 9 10 f=open('a.txt','w') #在windows上无法把f当做参数传入,可以传入一个文件名,然后在work内用a+的方式打开文件,进行写入测试 11 for i in range(5): 12 p=Process(target=work,args=(f,str(i))) 13 p.start()
4、队列:进程间通信(IPC)之一
进程彼此之间互相隔离,要实现进程间通信,即IPC,multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的
1.方法条用队列使用
调用方法:
Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。
参数说明:
maxsize是队列中允许最大项数,省略则无大小限制。
方法介绍:
a.主要方法
q.put 方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。
q.get 方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常.
q.get_nowait(): 同q.get(False)
q.put_nowait(): 同q.put(False)
q.empty(): 调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
q.full(): 调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。
q.qsize(): 返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样
b.其他方法
q.cancel_join_thread(): 不会在进程退出时自动连接后台线程。可以防止join_thread()方法阻塞
q.close(): 关闭队列,防止队列中加入更多数据。调用此方法,后台线程将继续写入那些已经入队列但尚未写入的数据,但将在此方法完成时马上关闭。如果q被垃圾收集,将调用此方法。关闭队列不会在队列使用者中产生任何类型的数据结束信号或异常。例如,如果某个使用者正在被阻塞在get()操作上,关闭生产者中的队列不会导致get()方法返回错误。
q.join_thread(): 连接队列的后台线程。此方法用于在调用q.close()方法之后,等待所有队列项被消耗。默认情况下,此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread方法可以禁止这种行为
1)使用示例:
1 ''' 2 multiprocessing模块支持进程间通信的两种主要形式:管道和队列 3 都是基于消息传递实现的,但是队列接口 4 ''' 5 6 from multiprocessing import Process,Queue 7 import time 8 q=Queue(3) 9 10 11 #put ,get ,put_nowait,get_nowait,full,empty 12 q.put(3) 13 q.put(3) 14 q.put(3) 15 print(q.full()) #满了 16 17 print(q.get()) 18 print(q.get()) 19 print(q.get()) 20 print(q.empty()) #空了
2)生产者消费者模型
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
基于队列实现生产者消费者模型
1 from multiprocessing import Process,Queue 2 import time,random,os 3 4 def consumer(q): 5 while True: 6 time.sleep(random.randint(1,3)) 7 res=q.get() 8 print('