目录:
- 一 、multiprocessing模块介绍
- 二 、Process类的介绍
- 三 、Process类的使用
- 四 、守护进程
- 五 、进程同步(锁)
- 六 、队列(推荐使用)
- 七 、管道
- 八 、信号量(了解)
- 九 、事件(了解)
- 十、 进程池
一、multiprocessing模块介绍
python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。Python提供了multiprocessing。 multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。
multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。
需要再次强调的一点是:与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内。
二、Process类的介绍
创建进程的类:
Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动) 强调: 1. 需要使用关键字的方式来指定参数 2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号
参数介绍:
group参数未使用,值始终为None target表示调用对象,即子进程要执行的任务 args表示调用对象的位置参数元组,args=(1,2,'egon',) kwargs表示调用对象的字典,kwargs={'name':'egon','age':18} name为子进程的名称
方法介绍:
p.start():启动进程,并调用该子进程中的p.run()
p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
p.is_alive():如果p仍然运行,返回True
p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程
属性介绍:
p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
p.name:进程的名称
p.pid:进程的pid
p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)
三、Process类的使用
注意:在windows中Process()必须放在:if __name__ == "__main__"下
详细解释:
Since Windows has no fork, the multiprocessing module starts a new Python process and imports the calling module. If Process() gets called upon import, then this sets off an infinite succession of new processes (or until your machine runs out of resources). This is the reason for hiding calls to Process() inside if __name__ == "__main__" since statements inside this if-statement will not get called upon import. 由于Windows没有fork,多处理模块启动一个新的Python进程并导入调用模块。 如果在导入时调用Process(),那么这将启动无限继承的新进程(或直到机器耗尽资源)。 这是隐藏对Process()内部调用的原,使用if __name__ == “__main __”,这个if语句中的语句将不会在导入时被调用。
创建并开启子进程的两种方式:
方式一:
import time # import random from multiprocessing import Process def piao(name): print('%s piaoing' %name) # time.sleep(random.randrange(1,5)) time.sleep(1) print('%s piao end' %name) if __name__ == '__main__': p1=Process(target=piao,args=('egon',)) #必须加,号 p2=Process(target=piao,args=('alex',)) p3=Process(target=piao,args=('wupeqi',)) p4=Process(target=piao,args=('yuanhao',)) p1.start() p2.start() p3.start() p4.start() print('主线程') ''' 主线程 egon piaoing alex piaoing wupeqi piaoing yuanhao piaoing egon piao end alex piao end wupeqi piao end yuanhao piao end
#注意:
1、p1.start()是告诉操作系统去申请内存空间,然后运行代码,之后p1.start()这行代码就完事了,继续往下运行print()
2、申请内存空间然后重新运行代码耗时长,速度肯定print代码运行速度快,因此先打印"主线程"
'''
方法二:
#开进程的方法二: import time # import random from multiprocessing import Process class Piao(Process): def __init__(self,name): super().__init__() self.name=name def run(self): print('%s piaoing' %self.name) # time.sleep(random.randrange(1,5)) time.sleep(1) print('%s piao end' %self.name) if __name__ == '__main__': p1=Piao('egon') p2=Piao('alex') p3=Piao('wupeiqi') p4=Piao('yuanhao') p1.start() #start会自动调用run p2.start() p3.start() p4.start() print('主线程') ''' 主线程 egon piaoing alex piaoing wupeiqi piaoing yuanhao piaoing egon piao end alex piao end wupeiqi piao end yuanhao piao end '''
总结:
""" 创建进程就是在内存中申请一块内存空间将需要运行的代码丢进去 一个进程对应在内存中就是一块独立的内存空间 多个进程对应在内存中就是多块独立的内存空间 进程与进程之间数据默认情况下是无法直接交互,如果想交互可以借助于第三方工具、模块 """
join方法的使用
join是让主进程等待子进程代码运行结束之后,再继续运行。不影响其他子进程的执行
from multiprocessing import Process import time def task(name, n): print('%s is running'%name) time.sleep(n) print('%s is over'%name) if __name__ == '__main__': # p1 = Process(target=task, args=('jason', 1)) # p2 = Process(target=task, args=('egon', 2)) # p3 = Process(target=task, args=('tank', 3)) # start_time = time.time() # p1.start() # p2.start() # p3.start() # 仅仅是告诉操作系统要创建进程 # # time.sleep(50000000000000000000) # # p.join() # 主进程等待子进程p运行结束之后再继续往后执行 # p1.join() # p2.join() # p3.join() start_time = time.time() p_list = [] for i in range(1, 4): p = Process(target=task, args=('子进程%s'%i, i)) p.start() p_list.append(p) for p in p_list: p.join() print('主', time.time() - start_time)
执行结果:
''' 子进程1 is running 子进程2 is running 子进程3 is running 子进程1 is over 子进程2 is over 子进程3 is over 主 3.0982506275177 '''
进程之间的内存空间是隔离的
from multiprocessing import Process n=100 #在windows系统中应该把全局变量定义在if __name__ == '__main__'之上就可以了 def work(): global n n=0 print('子进程内: ',n) if __name__ == '__main__': p=Process(target=work) p.start() print('主进程内: ',n) """ 主进程内: 100 子进程内: 0 """
Process对象的其他方法或属性(了解)
terminate与is_alive
""" from multiprocessing import Process, current_process current_process().pid # 查看当前进程的进程号 import os os.getpid() # 查看当前进程进程号 os.getppid() # 查看当前进程的父进程进程号 p.terminate() # 杀死当前进程 # 是告诉操作系统帮你去杀死当前进程 但是需要一定的时间 而代码的运行速度极快 time.sleep(0.1) print(p.is_alive()) # 判断当前进程是否存活
windows命令:
#tasklist |findstr PID #同ps -aux|grep PID ```
#进程对象的其他方法一:terminate,is_alive from multiprocessing import Process import time import random class Piao(Process): def __init__(self,name): self.name=name super().__init__() def run(self): print('%s is piaoing' %self.name) time.sleep(random.randrange(1,5)) print('%s is piao end' %self.name) p1=Piao('egon1') p1.start() p1.terminate()#关闭进程,不会立即关闭,所以is_alive立刻查看的结果有可能还是存活的 print(p1.is_alive()) #结果为True # print('开始') time.sleep(0.1) print(p1.is_alive()) #结果为False """ True False """
name与pid
from multiprocessing import Process,current_process import time import random import os class Piao(Process): def __init__(self,name): # self.name=name # super().__init__() #Process的__init__方法会执行self.name=Piao-1, # #所以加到这里,会覆盖我们的self.name=name #为我们开启的进程设置名字的做法 super().__init__() self.name=name def run(self): print("当前进程的进程号%s"%(current_process().pid)) print('%s is piaoing,当前进程的进程号pid:%s,当前进程的父进程进程号ppid:%s' %(self.name,os.getpid(),os.getppid())) time.sleep(random.randrange(1,3)) print('%s is piao end' %self.name) if __name__ == '__main__': p=Piao('egon') p.start() print('主:开始') print("主:当前进程的进程号%s" % (current_process().pid)) print('主:当前进程的进程号pid:%s,当前进程的父进程进程号ppid:%s' % (os.getppid(), os.getppid())) print("p进程",p.pid) #查看pid print("==============================") """ 主:开始 主:当前进程的进程号8560 主:当前进程的进程号pid:16548,当前进程的父进程进程号ppid:16548 p进程 11868 ============================== 当前进程的进程号11868 egon is piaoing,当前进程的进程号pid:11868,当前进程的父进程进程号ppid:8560 egon is piao end """
僵尸进程与孤儿进程(了解)
# 僵尸进程 """ 死了但是没有死透 当你开设了子进程之后 该进程死后不会立刻释放占用的进程号 因为我要让父进程能够查看到它开设的子进程的一些基本信息 占用的pid号 运行时间。。。 所有的进程都会步入僵尸进程 父进程不死并且在无限制的创建子进程并且子进程也不结束 回收子进程占用的pid号 父进程等待子进程运行结束 父进程调用join方法 """ # 孤儿进程 """ 子进程存活,父进程意外死亡 操作系统会开设一个“儿童福利院”专门管理孤儿进程回收相关资源 """
一:僵尸进程(有害) 僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。详解如下 我们知道在unix/linux中,正常情况下子进程是通过父进程创建的,子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束,如果子进程一结束就立刻回收其全部资源,那么在父进程内将无法获取子进程的状态信息。 因此,UNⅨ提供了一种机制可以保证父进程可以在任意时刻获取子进程结束时的状态信息: 1、在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等) 2、直到父进程通过wait / waitpid来取时才释放. 但这样就导致了问题,如果进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。 任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。 如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。 二:孤儿进程(无害) 孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。 孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。 我们来测试一下(创建完子进程后,主进程所在的这个脚本就退出了,当父进程先于子进程结束时,子进程会被init收养,成为孤儿进程,而非僵尸进程),文件内容 import os import sys import time pid = os.getpid() ppid = os.getppid() print 'im father', 'pid', pid, 'ppid', ppid pid = os.fork() #执行pid=os.fork()则会生成一个子进程 #返回值pid有两种值: # 如果返回的pid值为0,表示在子进程当中 # 如果返回的pid值>0,表示在父进程当中 if pid > 0: print 'father died..' sys.exit(0) # 保证主线程退出完毕 time.sleep(1) print 'im child', os.getpid(), os.getppid() 执行文件,输出结果: im father pid 32515 ppid 32015 father died.. im child 32516 1 看,子进程已经被pid为1的init进程接收了,所以僵尸进程在这种情况下是不存在的,存在只有孤儿进程而已,孤儿进程声明周期结束自然会被init来销毁。 三:僵尸进程危害场景: 例如有个进程,它定期的产 生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程 退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵死进程,倘若用ps命令查看的话,就会看到很多状态为Z的进程。 严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大 量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进 程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程 就能瞑目而去了。 四:测试 #1、产生僵尸进程的程序test.py内容如下 #coding:utf-8 from multiprocessing import Process import time,os def run(): print('子',os.getpid()) if __name__ == '__main__': p=Process(target=run) p.start() print('主',os.getpid()) time.sleep(1000) #2、在unix或linux系统上执行 [root@vm172-31-0-19 ~]# python3 test.py & [1] 18652 [root@vm172-31-0-19 ~]# 主 18652 子 18653 [root@vm172-31-0-19 ~]# ps aux |grep Z USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 18653 0.0 0.0 0 0 pts/0 Z 20:02 0:00 [python3] <defunct> #出现僵尸进程 root 18656 0.0 0.0 112648 952 pts/0 S+ 20:02 0:00 grep --color=auto Z [root@vm172-31-0-19 ~]# top #执行top命令发现1zombie top - 20:03:42 up 31 min, 3 users, load average: 0.01, 0.06, 0.12 Tasks: 93 total, 2 running, 90 sleeping, 0 stopped, 1 zombie %Cpu(s): 0.0 us, 0.3 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 1016884 total, 97184 free, 70848 used, 848852 buff/cache KiB Swap: 0 total, 0 free, 0 used. 782540 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND root 20 0 29788 1256 988 S 0.3 0.1 0:01.50 elfin #3、 等待父进程正常结束后会调用wait/waitpid去回收僵尸进程 但如果父进程是一个死循环,永远不会结束,那么该僵尸进程就会一直存在,僵尸进程过多,就是有害的 解决方法一:杀死父进程 解决方法二:对开启的子进程应该记得使用join,join会回收僵尸进程 参考python2源码注释 class Process(object): def join(self, timeout=None): ''' Wait until child process terminates ''' assert self._parent_pid == os.getpid(), 'can only join a child process' assert self._popen is not None, 'can only join a started process' res = self._popen.wait(timeout) if res is not None: _current_process._children.discard(self) join方法中调用了wait,告诉系统释放僵尸进程。discard为从自己的children中剔除 解决方法三:http://blog.csdn.net/u010571844/article/details/50419798
思考:
from multiprocessing import Process import time,os def task(): print('%s is running' %os.getpid()) time.sleep(3) if __name__ == '__main__': p=Process(target=task) p.start() p.join() # 等待进程p结束后,join函数内部会发送系统调用wait,去告诉操作系统回收掉进程p的id号 print(p.pid) #???此时能否看到子进程p的id号 print('主')
答案:
#答案:可以 #分析: p.join()是像操作系统发送请求,告知操作系统p的id号不需要再占用了,回收就可以, 此时在父进程内还可以看到p.pid,但此时的p.pid是一个无意义的id号,因为操作系统已经将该编号回收 打个比方: 我党相当于操作系统,控制着整个中国的硬件,每个人相当于一个进程,每个人都需要跟我党申请一个身份证号 该号码就相当于进程的pid,人死后应该到我党那里注销身份证号,p.join()就相当于要求我党回收身份证号,但p的家人(相当于主进程) 仍然持有p的身份证,但此刻的身份证已经没有意义
四、守护进程(一定要在start()前设置)
主进程创建守护进程
其一:守护进程会在主进程代码执行结束后就终止
其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止
from multiprocessing import Process import time import random class Piao(Process): def __init__(self,name): self.name=name super().__init__() def run(self): print('%s is piaoing' %self.name) time.sleep(random.randrange(1,3)) print('%s is piao end' %self.name) p=Piao('egon') p.daemon=True #一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且主进程代码执行结束,p即终止运行 p.start() print('主') ''' 执行结果 主 '''
迷惑的例子:
#主进程代码运行完毕,守护进程就会结束 from multiprocessing import Process from threading import Thread import time def foo(): print(123) time.sleep(1) print("end123") def bar(): print(456) time.sleep(3) print("end456") if __name__ == '__main__': p1=Process(target=foo) p2=Process(target=bar) p1.daemon=True p1.start() p2.start() print("main-------") #打印该行则主进程代码结束,则守护进程p1应该被终止,可能会有p1任务执行的打印信息123, # 因为主进程打印main----时,p1也执行了,但是随即被终止 ''' main------- 456 end456 '''
五、进程同步锁(互斥锁)
多个进程操作同一份数据的时候,会出现数据错乱的问题
针对上述问题,解决方式就是:
加锁处理:
将并发变成串行,牺牲效率但是保证了数据的安全
进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,
而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理
part1:多个进程共享同一打印终端
并发运行,效率高,但竞争同一打印终端,带来了打印错乱
from multiprocessing import Process import os,time def work(): print('%s is running' %os.getpid()) time.sleep(2) print('%s is done' %os.getpid()) if __name__ == '__main__': for i in range(3): p=Process(target=work) p.start() ''' 16304 is running 15540 is running 13992 is running 16304 is done 15540 is done 13992 is done '''
加锁:由并发变成了串行,牺牲了运行效率,但避免了竞争
#由并发变成了串行,牺牲了运行效率,但避免了竞争 from multiprocessing import Process,Lock #Lock锁模块 import os,time def work(lock): #子进程开始,加上锁 lock.acquire() print('%s is running' %os.getpid()) time.sleep(2) print('%s is done' %os.getpid()) lock.release() #子进程结束,释放锁 if __name__ == '__main__': #设置锁 lock=Lock() for i in range(3): p=Process(target=work,args=(lock,)) #把锁传给子进程,让所有子进程共用锁 p.start() ''' 1128 is running 1128 is done 11700 is running 11700 is done 15568 is running 15568 is done '''
part2:多个进程共享同一文件
文件当数据库,模拟抢票
并发运行,效率高,但竞争写同一文件,数据写入错乱
#文件db.txt的内容为:{"count":1} #注意一定要用双引号,不然json无法识别 from multiprocessing import Process import time,json,random def search(name): with open("db.txt","r",encoding="utf-8") as f: dic=json.load(f) print('用户%s剩余票数%s' %(name,dic['count'])) def get(name): #先查票 with open("db.txt","r",encoding="utf-8") as f: dic=json.load(f) time.sleep(random.randint(1,3)) #模拟读数据的网络延迟 if dic.get("count") >0: dic['count']-=1 time.sleep(0.2) #模拟写数据的网络延迟 with open("db.txt", "w", encoding="utf-8") as f: json.dump(dic,f) print('用户{}购票成功'.format(name)) else: print("用户{}购票失败".format(name)) def task(name): search(name) get(name) if __name__ == '__main__': for i in range(1,11): #模拟并发10个客户端抢票 p=Process(target=task,args=(i,)) p.start() """ 用户1剩余票数1 用户2剩余票数1 用户3剩余票数1 用户4剩余票数1 用户5剩余票数1 用户6剩余票数1 用户7剩余票数1 用户8剩余票数1 用户9剩余票数1 用户10剩余票数1 用户5购票成功 用户7购票成功 用户1购票成功 用户6购票成功 用户9购票成功 用户10购票成功 用户2购票成功 用户3购票成功 用户4购票成功 用户8购票成功 """
加锁:购票行为由并发变成了串行,牺牲了运行效率,但保证了数据安全
# 文件db的内容为:{"count":1} # 注意一定要用双引号,不然json无法识别 from multiprocessing import Process, Lock # 导入锁模块Lock import time, json, random def search(name): with open("db.txt", "r", encoding="utf-8") as f: dic = json.load(f) print('用户%s剩余票数%s' % (name, dic['count'])) def get(name): # 先查票 with open("db.txt", "r", encoding="utf-8") as f: dic = json.load(f) time.sleep(random.randint(1, 3)) # 模拟读数据的网络延迟 if dic.get("count") > 0: dic['count'] -= 1 time.sleep(0.2) # 模拟写数据的网络延迟 with open("db.txt", "w", encoding="utf-8") as f: json.dump(dic, f) print('用户{}购票成功'.format(name)) else: print("用户{}购票失败".format(name)) def task(name, mutex): search(name) # 给买票环节加锁处理 # 子进程开始,加上锁 mutex.acquire() get(name) # 解锁 mutex.release() if __name__ == '__main__': # 设置锁 mutex = Lock() for i in range(1, 11): # 模拟并发10个客户端抢票 p = Process(target=task, args=(i, mutex)) # 把锁传给子进程,让所有子进程共用锁 p.start() """ 用户1剩余票数1 用户2剩余票数1 用户3剩余票数1 用户4剩余票数1 用户5剩余票数1 用户6剩余票数1 用户7剩余票数1 用户8剩余票数1 用户10剩余票数1 用户9剩余票数1 用户1购票成功 用户2购票失败 用户3购票失败 用户4购票失败 用户5购票失败 用户6购票失败 用户7购票失败 用户8购票失败 用户10购票失败 用户9购票失败 """
总结:
#加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。 虽然可以用文件共享数据实现进程间通信,但问题是: 1.效率低(共享数据基于文件,而文件是硬盘上的数据) 2.需要自己加锁处理 #因此我们最好找寻一种解决方案能够兼顾:1、效率高(多个进程共享一块内存的数据)2、帮我们处理好锁问题。这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。 1 队列和管道都是将数据存放于内存中 2 队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来, 我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。 #注意: #锁不要轻易的使用,容易造成死锁现象
六、队列(推荐使用)
进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的
创建队列的类(底层就是以管道和锁定的方式实现):
Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。
参数介绍:
maxsize是队列中允许最大项数,省略则无大小限制。
方法介绍:
主要方法:
1、q.put(data,blocked=True,timeout=正值)方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。
如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。
注意:timeout默认没有指定,此时如果队列满了,再添加值因为blocked默认为True,所以此时会一直阻塞,等待队列中有数据出去,这个put的数据再进入
2、q.get(data,blocked=True,timeout=正值)方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。
如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,
会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常.
3、q.get_nowait():没有数据直接报错queue.Empty
4、q.put_nowait():如果队列满了不能添加值,直接报错queue.Full
5、q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
6、q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。
7、q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样
其他方法:
1、q.cancel_join_thread():不会在进程退出时自动连接后台线程。可以防止join_thread()方法阻塞
2、q.close():关闭队列,防止队列中加入更多数据。调用此方法,后台线程将继续写入那些已经入队列但尚未写入的数据,但将在此方法完成时马上关闭。如果q被垃圾收集,将调用此方法。
关闭队列不会在队列使用者中产生任何类型的数据结束信号或异常。例如,如果某个使用者正在被阻塞在get()操作上,关闭生产者中的队列不会导致get()方法返回错误。
3、q.join_thread():连接队列的后台线程。此方法用于在调用q.close()方法之后,等待所有队列项被消耗。默认情况下,此方法由不是q的原始创建者的所有进程调用。
调用q.cancel_join_thread方法可以禁止这种行为
应用实例:
''' multiprocessing模块支持进程间通信的两种主要形式:管道和队列 都是基于消息传递实现的,但是队列接口 ''' from multiprocessing import Process, Queue import time q = Queue(3) # put ,get ,put_nowait,get_nowait,full,empty q.put(3) q.put(3) q.put(3) print(q.full()) # 满了,打印为True # 满了再添加值,设置timeout # q.put(3,timeout=3) #三秒后会报错queue.Full print(q.get()) print(q.get()) print(q.get()) print(q.empty()) # 空了,打印为True # 空了之后再取值 # print(q.get(timeout=3)) #没有数据之后原地等待三秒之后再报错 queue.Empty # print(q.get_nowait()) #没有数据直接报错queue.Empty
总结:
q.get_nowait():没有数据直接报错queue.Empty,该结果不可靠
q.put_nowait():如果队列满了不能添加值,直接报错queue.Full,该结果不可靠
q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。
生产者消费者模型:
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
基于队列实现生产者消费者模型
from multiprocessing import Process,Queue import time,random,os def consumer(q): while True: res=q.get() time.sleep(random.randint(1,3)) print('%s 吃 %s' %(os.getpid(),res)) def producer(q): for i in range(1,11): time.sleep(random.randint(1,3)) res='包子%s' %i q.put(res) print('%s 生产了 %s' %(os.getpid(),res)) if __name__ == '__main__': q=Queue() #生产者们:即厨师们 p1=Process(target=producer,args=(q,)) #消费者们:即吃货们 c1=Process(target=consumer,args=(q,)) #开始 p1.start() c1.start() print('主')
生产者消费者模型总结:
#生产者消费者模型总结 #程序中有两类角色 一类负责生产数据(生产者) 一类负责处理数据(消费者) #引入生产者消费者模型为了解决的问题是: 平衡生产者与消费者之间的工作能力,从而提高程序整体处理数据的速度 #如何实现: 生产者<-->队列<——>消费者 #生产者消费者模型实现类程序的解耦和
此时的问题是主进程永远不会结束,原因是:生产者p在生产完后就结束了,但是消费者c在取空了q之后,则一直处于死循环中且卡在q.get()这一步。
解决方式无非是让生产者在生产完毕后,往队列中再发一个结束信号,这样消费者在接收到结束信号后就可以break出死循环
生产者在生产完毕后发送结束信号None
from multiprocessing import Process,Queue import time,random,os def consumer(q): while True: res=q.get() if res is None:break #接收到结束信号结束 time.sleep(random.randint(1,3)) print('%s 吃 %s' %(os.getpid(),res)) def producer(q): for i in range(1,11): time.sleep(random.randint(1,3)) res='包子%s' %i q.put(res) print('%s 生产了 %s' %(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,不一定要由生产者发,主进程里同样可以发,但主进程需要等生产者结束后才应该发送该信号
主进程在生产者生产完毕后发送结束信号None
from multiprocessing import Process,Queue import time,random,os def consumer(q): while True: res=q.get() if res is None:break #收到结束信号则结束 time.sleep(random.randint(1,3)) print('