本文继续Python多任务编程思想(一)的话题,讨论python中进程的剩余几个概念:守护进程、重写Process类创建子进程以及进程池技术。
一. 守护进程
通过上一小节的学习,我们已经知道主进程创建守护进程只需要将Process的daemon属性为true即可。虽然进程间是相互独立的,守护进程会在主进程"代码执行结束"时终止;③守护进程中不能再创建子进程。
守护进程与守护线程存在很大差异,在学习完守护线程后,将对二者做出总结区分。
'''p1为守护进程,在主进程退出时,p1结束循环;p2为非守护进程,不会受到主进程的影响'''
import time, os, signal
from multiprocessing import Process
def foo():
while True:
print(time.ctime())
time.sleep(3)
def bar():
print("非守护进程启动")
time.sleep(60)
print("非守护进程结束")
p1=Process(target=foo)
p2=Process(target=bar)
#避免守护进程成为僵尸进程
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
if __name__ == '__main__':
p1.daemon=True
p1.start()
p2.start()
print("在主进程{}中创建了守护进程{}和非守护进程{}".format(os.getpid(), p1.pid, p2.pid))
time.sleep(4)
print("主进程{}结束!".format(os.getpid()))
运行结果:在打印主进程22828结束提示时,查看进程信息,可以看到守护进程22829已经退出,留下主进程22828和其子进程22830。提示非守护进程22830也运行结束时,再次查看进程,22828和22830均已退出。我们在上一小节提到mutiprocessing创建的子进程在执行结束后会返回主进程,所以我们查看进程信息时,主进程任然是存在的,只有守护进程22829因主进程代码执行完毕而退出。因此,我们的非守护进程并非孤儿进程,通过设置signal.signal(signal.SIGCHLD, signal.SIG_IGN),在主进程收到子进程退出的SIGCHLD信号时,做忽略处理,以交由系统处理,以此来避免僵尸的产生。
二. 创建自己的进程类
除了使用Process类在实例化时传入target函数外,另一种常用的方式,还可以继承Process类,并重写run方法,run方法将在Process实例调用start方法后,自动运行。
from multiprocessing import Process
import time
class ClockProcess(Process):
def __init__(self, value):
# 调用父类的__init__方法
super().__init__()
self.value = value
def run(self):
'''重写run方法,子进程启动后,自动执行此方法'''
for i in range(3):
time.sleep(self.value)
print(time.ctime())
p1 = ClockProcess(2)
p1.start()
p1.join()
三. 进程池技术
多进程编程能并行执行多个任务,具有效率高,创建方便,运行独立,不受其他进程影响,数据安全等特点;但进程的创建和删除都需要消耗计算机的资源。如果有大量任务需要多进程完成,且可能需要频繁地创建和删除进程,将给计算机带来大量的资源消耗。为此,我们使用进程池,在进程池内运行一定数量的进程,通过这些进程完成进程池队列中的事件,直到事件执行完,减少进程的不断地创建删除过程。进程池的过程实现如下:
- 创建进程池,在进程池中放入适当进程
- 将事件加入到进程池队列
- 事件不断运行,直到所有事件运行完毕
- 关闭进程池,回收进程
上述过程用到的multiprocessing模块的类/方法总结如下:
multiprocessing 类/方法 | 说明 |
from multiprocessing import Pool pool = Pool(processes) | 创建进程池对象 参数processes表示进程池中有多少个进程 |
pool.apply_async(func, args, kwds) | 将事件放入进程池队列 参数:func要执行的事件,args给func用元组传参,kwds 给func用字典传参 返回值:事件对象,可通过get()方法获取事件函数返回值 |
pool.map(func, iter) | 将要完成的事件放入进程池 参数:func 要完成的事件函数,iter 可迭代对象给func传值 返回值:事件函数的返回值列表 |
pool.close() | 关闭进程池,不能再添加新的事件 |
pool.join() | 阻塞等待回收进程池 |
3.1 apply_async 将事件放入进程池队列
'''方式一:使用"apply_async()"需要自己创建列表,维护返回值'''
import os
from multiprocessing import Pool
from time import sleep,ctime
def event(message):
sleep(2)
print("{} 子进程{} message={}".format(ctime(), os.getpid(), message))
return '&' + str(message) + '&'
print(ctime(), '父进程PID:', os.getpid())
pool = Pool(4)
r_list = []
for i in range(10):
element = pool.apply_async(event,(i,))
r_list.append(element)
for e in r_list:
print(ctime(), "事件函数返回值:", e.get())
pool.close()
print(ctime(), '父进程{}阻塞等待回进程池.'.format(os.getpid()))
pool.join()
运行结果:使用线程池完成event的10次调用总耗时只有6s,而如果是单线程最少需要10*2 s。进程池中的进程并行执行,一起返回,直到所有进程都执行完毕。
3.2 更简洁的方式map
'''方法二 使用进程池的map方法;返回值为列表,无需自己创建维护列表'''
import os
from multiprocessing import Pool
from time import sleep,ctime
def event(message):
sleep(2)
print("{} 子进程{} message={}".format(ctime(), os.getpid(), message))
return '&' + str(message) + '&'
pool = Pool(4)
print(ctime(), '父进程PID:', os.getpid())
l = pool.map(event, range(10))
print(l)
pool.close()
print(ctime(), '父进程{}阻塞等待回进程池.'.format(os.getpid()))
pool.join()
运行结果:使用pool.map向进程池添加事件函数时,在父进程中获取返回值列表时是阻塞的,需要等待进程池返回所有事件函数的执行结果。但耗时上和apply_async是一致的。