目录
一、multiprocessing模块介绍
二、进程
三、Process类
四、Process对象的join方法
五、守护进程
一、multiprocessing模块介绍
python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu\_count\(\)查看),在python中大部分情况需要使用多进程。
Python提供了multiprocessing。 multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,>提供了Process、Queue、Pipe、Lock等组件。
需要再次强调的一点是:与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内。
二、进程
2.1 进程的理论
进程:进程是指一个程序在一个数据集上的动态执行过程(程序执行过程的抽象)
进程与程序的区别:程序仅仅是一堆代码而已,而进程指的是程序运行的过程。
进程运行的三种状态:
- 运行:就绪态的进程被分配到CPU,进入运行状态;
- 阻塞:进程执行过程中出现IO,进入阻塞状态(操作系统强行剥夺CPU),可以通过减少阻塞有效的来提高效率;
- 就绪:等待CPU来执行的过程。进程占用CPU时间过长/出现一个优先级更高的进程,进入就绪状态(CPU被剥夺)。
2.2 开启进程的两种方式
方式一:调用内置的类
1 from multiprocessing import Process 2 import time 3 4 def task(name): 5 print("%s is running" % name) 6 time.sleep(3) 7 print("%s is done" % name) 8 9 if __name__ == "__main__": 10 # Process(target=task,kwargs={'name':'子进程1'}) 11 p=Process(target=task,args=('子进程1',)) #args只有一个参数时,最后一定要加逗号,保证远足形式传递参数 12 p.start() #仅仅只是给操作系统发送了一个信号,等待cpu调度 13 print("主")
方式二:自定义类
1 from multiprocessing import Process 2 import time 3 4 5 class MyProcess(Process): 6 def __init__(self,name): 7 super().__init__() 8 self.name=name 9 10 def run(self): #必须是run这个名字 11 print('%s is running' % self.name) 12 time.sleep(3) 13 print('%s is done' % self.name) 14 15 16 if __name__ == '__main__': 17 p=MyProcess('子进程1') 18 p.start() #本质上是在调用父类的start方法,而start方法下会触发run方法 19 print('主')
注意:在windows中Process()必须放到if __name == "__main__":下。由于在windows系统下,子进程是通过导入模块的方式拿到父进程的代码,如果没有main会一直开启子进程,而子进程的申请是需要开辟内存以及申请pid等。
三、Process类
Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,可用来开启一个子进程。
强调:
- 需要使用关键字的方式来指定参数。如:target =×××,arges=(×××,)
- args制定的为传给taget函数的位置参数,是一个元组形式,必须有逗号。
参数介绍:
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开启的进程
四、Process对象的join方法
在主进程运行过程中,如果想并发的执行其他任务,可以通过开启子进程实现,此时子进程的任务与主进程的任务分两种情况:
- 在主进程的任务与子进程的任务彼此独立的情况下,主进程的任务先执行完毕,主进程还需要等待子进程执行完毕,然后统一回收资源。
- 如果住进成的任务在执行到某一阶段时,需要等待子进程执行完毕后才能继续执行,就需要一种机制能够让主进程检测子进程是否执行完毕。主进程在子进程执行完毕后才继续执行,否则一直在原地阻塞。这就时join方法的作用。
举例如下:
1 from multiprocessing import Process 2 import time,os 3 4 def task(name,n): 5 print('%s is running' %name) 6 time.sleep(n) 7 8 9 if __name__ == '__main__': 10 start=time.time() 11 p1=Process(target=task,args=('子进程1',5)) 12 p2=Process(target=task,args=('子进程2',3)) 13 p3=Process(target=task,args=('子进程3',2)) 14 p_l=[p1,p2,p3] 15 16 # p1.start() 17 # p2.start() 18 # p3.start() 19 for p in p_l: 20 p.start() 21 22 # p1.join() 23 # p2.join() 24 # p3.join() 25 for p in p_l: 26 p.join() 27 28 print('主',(time.time()-start))
说明:
p.join时让主进程等待p的结束,卡住的是主进程而不是子进程。
进程只要start就会在开始运行了,所以p1-p4.start()时,系统中已经有四个并发的进程了。
join是让主线程等,而p1-p3仍然是并发执行的,p1.join的时候,其余p2,p3仍然在运行,等p1.join结束,可能p2,p3早已经结束了,这样p2.join,p3.join直接通过检测,无需等待。所以3个join花费的总时间仍然是耗费时间最长的那个进程运行的时间
五、守护进程
主进程创建子进程,然后将该进程设置成守护自己的进程。关于守护进程需要强调两点:
- 守护进程在主进程代码执行结束就终止;
- 守护进程内无法在开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
1 from multiprocessing import Process 2 import time 3 import random 4 5 6 def task(name): 7 print('%s is running' % name) 8 time.sleep(random.randrange(1,3)) 9 print("%s has gone" % name) 10 11 12 if __name__ == '__main__': 13 p = Process(target=task,args=('John',)) 14 p.daemon = True #一定要在p.start()前设置,设置p为守护进程,机制p创建子进程,并且父进程代码之行结束,p即终止运行。 15 p.start() 16 print('主') #只要中断打印出这一行内容,那么守护进程P也就跟着结束了
- p.daemon = True 一定要在p.start()前设置,设置p为守护进程,机制p创建子进程,并且父进程代码之行结束,p即终止运行。
-
只要中断打印“print("主")”出这一行内容,那么守护进程P也就跟着结束了。
运行结果如下