python 进程 multiprocessing模块
Posted walthwang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python 进程 multiprocessing模块相关的知识,希望对你有一定的参考价值。
一、multiprocess.process模块
1.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为子进程的名称
2.process类的方法
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开启的进程
3.process类的属性
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字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)
4.在windows运行时要注意的问题
在Windows操作系统中由于没有fork(linux操作系统中创建进程的机制),在创建子进程的时候会自动 import 启动它的这个文件,而在 import 的时候又执行了整个文件。因此如果将process()直接写在文件中就会无限递归创建子进程报错。所以必须把创建子进程的部分使用if __name__ ==‘__main__’ 判断保护起来,import 的时候 ,就不会递归运行了。
5.实例代码
1)创建一个子进程,并运行
from multiprocessing import Process def func(name): print(‘子进程:你好,‘,name) if __name__ == ‘__main__‘: p = Process(target=func,args=(‘hsr‘,)) p.start()
2)查看子进程和父进程的进程号
from multiprocessing import Process import os def func(): print(‘子进程PID:‘,os.getpid()) if __name__ == ‘__main__‘: p = Process(target=func) p.start() print(‘父进程PID:‘,os.getpid())
3)join
from multiprocessing import Process import time def func(*args): print(‘*‘*args[0]) time.sleep(5) print(‘*‘ * args[1]) if __name__ == ‘__main__‘: p = Process(target=func,args=(10,20)) p.start() p.join() #主线程等待p终止 print("-------运行完了-------")
4)开启多个子进程
from multiprocessing import Process import time def func(no,*args): print(str(no)+" :"+‘*‘*args[0]) time.sleep(5) print(str(no)+" :"+‘*‘*args[1]) if __name__ == ‘__main__‘: p_li = [] for i in range(10): p_li.append(Process(target=func,args=(i,10,20))) for i in p_li: i.start() [i.join() for i in p_li] #让最后的print等子进程都结束了再执行 print(‘运行完了‘)
5)实现多进程的另一种方法
#自定义类 继承Process类 #必须实现run方法,run方法就是子进程执行的方法 #如果要参数,则实现自己的init方法,并在其中调用父类的init方法 from multiprocessing import Process import os class MyProcess(Process): def __init__(self,arg1): super().__init__() self.arg1 = arg1 def run(self): print("My Process:",self.pid) print(self.arg1) if __name__ == ‘__main__‘: print(os.getpid()) p1 = MyProcess(4) p1.start()
6.进程间的数据隔离
#进程间不会共享数据 from multiprocessing import Process import os def func(): global n n = 0 print(‘pid:‘+str(os.getpid())+" "+str(n)) if __name__ == ‘__main__‘: n = 100 p = Process(target=func) p.start() p.join() print(‘pid:‘+str(os.getpid())+" "+str(n))
7.守护进程
守护进程(daemon)是一类在后台运行的特殊进程,用于执行特定的系统任务。很多守护进程在系统引导的时候启动,并且一直运行直到系统关闭。
会随着主进程的结束而结束。
主进程创建守护进程
其一:守护进程会在主进程代码执行结束后就终止
其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止
#守护进程 from multiprocessing import Process import time def func(): while 1: time.sleep(2) print(‘Good‘) if __name__ == ‘__main__‘: p = Process(target=func) p.daemon = True #设置子进程为守护进程 p.start() i = 10 while i>0: print(‘Do something‘) time.sleep(5) i -= 1
二、进程同步
1.进程锁
#加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。
虽然可以用文件共享数据实现进程间通信,但问题是:
1.效率低(共享数据基于文件,而文件是硬盘上的数据)
2.需要自己加锁处理
#因此我们最好找寻一种解决方案能够兼顾:1、效率高(多个进程共享一块内存的数据)2、帮我们处理好锁问题。这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。
队列和管道都是将数据存放于内存中
队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,
我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。
#模拟吃50个人吃5个苹果 #使用Lock对象的acquire请求锁,release释放锁 from multiprocessing import Process from multiprocessing import Lock import json def eat(no,lock): lock.acquire() with open(‘info.json‘) as f: dic = json.load(f) AppleNum = dic["Apple"] print("苹果个数:" + str(AppleNum)) if AppleNum >0: print("%d 吃了一个苹果" %no) AppleNum -= 1 dic["Apple"] = AppleNum with open(‘info.json‘,‘w‘) as f: json.dump(dic,f) else: print("%d 没有苹果吃了" %no) lock.release() if __name__ == ‘__main__‘: lock = Lock() for i in range(50): Process(target=eat, args=(i,lock)).start()
2.信号量
信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。
信号量(semaphore)的数据结构为一个值和一个指针,指针指向等待该信号量的下一个进程。信号量的值与相应资源的使用情况有关。当它的值大于0时,表示当前可用资源的数量;当它的值小于0时,其绝对值表示等待使用该资源的进程个数。注意,信号量的值仅能由PV操作来改变。
对于信号量,可以认为是一个仓库,有两个概念,容量和当前的货物个数。
P操作从仓库拿货,如果仓库中没有货,线程一直等待,直到V操作,往仓库里添加了货物,为了避免P操作一直等待下去,会有一个超时时间。
V操作往仓库送货,如果仓库满了,线程等待,直到有P操作,从仓库中拿走货物,有空的位置。
创建信号量,设置容量,先有V操作,才能P操作。
P操作:货物个数减1,减过之后,货物个数大于等于0,说明已经拿到货物,线程继续。否者线程阻塞。
V操作:货物个数加1,加过之后,货物个数小于等于容量,说明添加成功,线程继续。否者线程阻塞。
信号量:0≤ 信号量≤容量 ,取值 表示当前可以使用的货物;
信号量<0 , 取值 表示当前等待使用货物的线程;
信号量>容量 , 信号量-容量 表示当前等待添加货物的线程。
通常,信号量的容量设置很大,可以一直V操作,不会阻塞,但是P操作的时候,很可能阻塞。
当容量为1,也就是互斥,执行流程必定是V操作,P操作,V操作,P操作...
P原语操作的动作是:
(1) sem减1;
(2) 若sem减1后仍大于或等于零,则进程继续执行;
(3) 若sem减1后小于零,则该进程被阻塞后进入与该信号相对应的队列中,然后转进程调度。
V原语操作的动作是:
(1) sem加1;
(2) 若相加结果大于零,则进程继续执行;
(3) 若相加结果小于或等于零,则从该信号的等待队列中唤醒一等待进程,然后再返回原进程继续执行或转进程调度。
from multiprocessing import Process,Semaphore import time import random def grid(i,sem): sem.acquire() print(str(i)+‘放入了格子‘) time.sleep(random.randint(2,6)) print(str(i)+‘拿出了格子‘) sem.release() if __name__ == ‘__main__‘: sem = Semaphore(4) for i in range(20): Process(target=grid,args=(i,sem)).start()
3.事件
通过一个信号来控制多个进程同时执行或阻塞
一个事件被创建后,默认是阻塞状态
from multiprocessing import Event if __name__ == "__main__": e = Event() # c创建一个事件 print(e.is_set()) # 查看一个事件的状态 e.set() #将事件的状态改为True e.wait() #根据e.is_set()的值决定是否阻塞 print(1235455) e.clear() #将事件的状态改为False e.wait() print(12323545555)
红绿灯问题
from multiprocessing import Event,Process import time import random def traffic_light(e): while 1: if e.is_set(): e.clear() print(‘