python中多进程的实现是使用multiprocessing模块下的Process类
首先导入from multiprocessing import Process
使用进程类创建一个进程实例:
p = Process([group [, target [, name [, args [, kwargs]]]]])
参数
group参数未使用,值始终为None
target表示调用对象,即子进程要执行的任务
args表示调用对象的位置参数元组,args=(1,2,'allen',),只有一个参数时,必须加上逗号,比如:
(1,)
kwargs表示调用对象的关键字参数,kwargs={'name':'allen','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开启的进程
简单使用
我们打开了pycharm,就会打开一个进程,我们把这个进程叫做管理pycharm的进程,也就是pycharm这个软件的进程,它是最初的一个进程。
可以理解为pycharm中当前操作的tab是由这个主进程来控制的
这个进程id为7332,不关闭pycharm的话,这个进程就一直存在
test.py
import multiprocessing
from multiprocessing import Process
import time
import os
#这个函数是要丢给子进程去处理的
def task(name):
print("子进程%s is running"%name)
print("子进程%s pid is %s"%(name,os.getpid()))
print('子进程%s parent is %s'%(name,os.getppid()))
time.sleep(5)
print("子进程%s is end"%name)
#这里是程序入口,run的时候也是创建了一个进程来执行,这个是主进程
if __name__ =="__main__":
#显示当前进程的名字
print('当前进程名称是%s'%multiprocessing.current_process().name)
print("当前进程的id is %s" % os.getpid())
print("%s的父进程id是%s"%(multiprocessing.current_process().name,os.getppid()))
print("我会创建一个子进程task")
print('\n')
p = Process(target=task,args=("task进程",))
#run() 方法并不启动一个新线程,就是在主线程中调用了一个普通函数而已。
p.start()
p.join() #主进程等待子进程结束后再继续执行
print("%s结束"%multiprocessing.current_process().name)
对于pycharm程序来说,pycharm的进程id是7332,
我们在test.py界面右键执行代码时,就会创建一个新的进程,这是进程是MainProcess,他的父进程就是pycharm进程。MainProcess在执行中又会创建一个子进程,来执行那个函数。
程序在运行过程中,我们发现有3个python的进程
三个进程的pid
7332是pycharm的进程id,主进程id是3436,他的父进程id是7332,也就是pycharm的进程id。7784则是主进程创建的另一个子进程的id。程序的输出为:
p.run()
这里还要注意的是,如果我们使用p.run()
方法启动进程,其实并不会启动一个新进程,比如
#这个函数是要丢给子进程去处理的
def task(name):
print("子进程task id为%s"%os.getpid())
#这里是程序入口,可以看做是主程序,其实也是一个进程来处理他
if __name__ =="__main__":
print('主进程的id为%s'%os.getpid())
print('我要创建一个新进程')
p = Process(target=task,args=("task进程",))
#run() 方法并不启动一个新线程,就是在主线程中调用了一个普通函数而已。
p.run()
print("进程__main__结束")
执行结果为:
结果是main进程和task进程的id是一样的,查了资料解释说:
run() 方法并不启动一个新线程,就是在主线程中调用了一个普通函数而已。
start()方法是启动一个子线程,线程名就是自己定义的name。
run()的源码也很简单
def run(self):
#Method to be run in sub-process; can be overridden in sub-class
if self._target:
#self._target = target,创建进程实例时传递的函数名称
self._target(*self._args, **self._kwargs)
也可以看到run()只是单纯的执行传递过来的函数,并没有创建新的进程。
当使用p.start()就不一样了
结果确实启动了一个新的进程
p.join()
测试代码:
import time
from multiprocessing import Process
import os
def task(name):
print('子进程开始...')
print('子进程运行中,%s is running'%name)
print('子进程id %s'%os.getpid())
time.sleep(2)
print('子进程结束...')
if __name__ == '__main__':
print('主进程开始了....')
print('主进程id %s'%os.getpid())
print('主进程马上要创建一个子进程')
p = Process(target=task,args=('子进程1',))
p.start()
print('主进程结束了')
运行这个程序的时候,主进程会比子进程结束的要早,因为主进程很快就执行完了,还没有到一个cpu的时间片,还没发生切换。如果想让主进程等到子进程结束后再执行,这时就可以使用p.join()。让主进程等待p结束,之后再运行。比如:
def task(name):
print('子进程开始...')
print('子进程运行中,%s is running'%name)
print('子进程id %s'%os.getpid())
# time.sleep(2)
print('子进程结束...')
if __name__ == '__main__':
print('主进程开始了....')
print('主进程id %s'%os.getpid())
print('主进程马上要创建一个子进程')
p = Process(target=task,args=('子进程1',))
p.start()
p.join()#让主进程等待p结束在继续执行,所以下面的语句要p结束才会执行
print('主进程结束了')
进程池
为什么要使用进程池呢?
在程序实际处理问题过程中,忙时会有成千上万的任务需要被执行,闲时可能只有零星任务。那么在成千上万个任务需要被执行的时候,我们就需要去创建成千上万个进程么?首先,创建进程需要消耗时间,销毁进程也需要消耗时间。第二即便开启了成千上万的进程,操作系统也不能让他们同时执行,这样反而会影响程序的效率。因此我们不能无限制的根据任务开启或者结束进程。那么我们要怎么做呢?
定义一个池子,在里面放上固定数量的进程,有需求来了,就拿一个池中的进程来处理任务,等到处理完毕,进程并不关闭,而是将进程再放回进程池中继续等待任务。如果有很多任务需要执行,池中的进程数量不够,任务就要等待之前的进程执行任务完毕归来,拿到空闲进程才能继续执行。也就是说,池中进程的数量是固定的,那么同一时间最多有固定数量的进程在运行。这样不会增加操作系统的调度难度,还节省了开闭进程的时间,也一定程度上能够实现并发效果。
参考:https://bbs.huaweicloud.com/blogs/154872
Python multiprocessing 模块提供了 Pool() 类,专门用来创建一个进程池,可以提供指定数量的进程供用户调用,该函数的语法格式如下:
multiprocessing.Pool( processes )
其中,processes 参数用于指定该进程池中包含的进程数。
如果processes是 None,则默认使用 os.cpu_count()
返回的数字(根据本地的 cpu 个数决定,processes 小于等于本地的 cpu 个数)。
def task(name):
print('进程%s开始了...'%name)
#最好写成 任务x交给了进程x来处理
#进程只有4个,任务是很多个的
print('进程%s的id 是%s'%(name,os.getpid()))
time.sleep(2)
print('进程%s结束了...'%name)
if __name__=='__main__':
print('主进程开始了...')
print('主进程id是%s'%os.getpid())
#进程池数量为4,表示创建了4个进程给你用
#执行这句代码后,就创建了4个进程,可在任务管理器中看到
p = Pool(4)
for i in range(10):
#这里有10个任务想要用多进程方式执行,但是只有4个进程给他们用
#给进程池中的进程分配任务,循环一次就是分配一个任务给一个进程,
#如果下次循环时,进程池中没有可以分配的进程了,就是说4个进程都在执行,那下一个任务就的等待
#等到某一个进程执行结束后,再把这个任务交给刚刚结束的进程来执行
p.apply_async(task,(i,))
p.close()
print('主进程结束了...')
我在p = Pool(4)
代码处设置了断点,通过调试可以发现执行完这句代码,就创建了4个进程,在任务管理器可以看到
执行之前:
执行之后
7392的进程id是主进程的id ,其他四个进程则是进程池中的4个进程。
应该说是,现在创建了这4个进程,但是他们并没有执行对应的任务。
接着往下调试可以发现,前面4个任务的进程id是不一样的,说明进程池中的4个进程没有全部在使用。
这里应该是任务几,因为进程只有4个,任务会更多
从第5个任务开始,可以发现进程id 是和前4个任务用的是一样的,因为前面创建的4个进程最开始在执行4个任务,第五个任务过来时,进程池中没有空闲的进程了,那这个任务就会等待,等到前面有任务处理完了,有进程空闲了,这个任务在交给这个空下来的进程处理。
Pool 类中提供了如下几个方法:
apply()
apply_async()
map()
map_async()
close()
terminal()
join()
这里主要说一下apply和apply_async两个:
apply()
apply():
阻塞主进程, 并且一个一个按顺序地执行子进程, 等到全部子进程都执行完毕后 ,继续执行 apply()后面主进程的代码。
函数的参数:
apply(self, func, args=(), kwds={}):
func:进程要执行的函数
args:函数携带的参数元组
kwds:函数的关键字参数
测试
import time
from multiprocessing import Pool
import multiprocessing
def task(num):
print('任务%s交给了进程%s处理'%(num,multiprocessing.current_process().name))
time.sleep(1)
print('任务%s结束了,进程%s执行完成'%(num,multiprocessing.current_process().name))
if __name__ == '__main__':
print('主进程开始了.........')
p = Pool(4)
#5个任务交个4个进程执行
for i in range(5):
p.apply(func=task,args=(i+1,)) #参数表示这是第几个任务
#关闭进程池
p.close()
print('主进程结束了.........')
首先主进程执行,主进程中创建了一个进程池对象,然后让这些进程阻塞式的执行,也就是先阻塞主进程,然后任务1对应的进程,任务2对应的进程…,一直到任务5对应的进程执行结束后,主进程才会接着执行。
输出结果为:
还有就是任务1并不一定就是进程1来执行,这个要看cpu的调度了。多执行几次就会发现,这是随机的。
apply_async():
apply_async()
非阻塞异步的, 他不会让主进程等待子进程执行完毕, 主进程会继续执行, 他会根据系统调度来进行进程切换。这些子任务之间也是随机切换着来执行的,不会再有等待谁这种情况。
函数参数:
apply_async(self, func, args=(), kwds={}, callback=None,error_callback=None):
使用apply_async()只要把刚才的apply改一下就行。执行结果为:
可以看到主进程直接执行,开始然后结束,没有等待任何进程。而且没有到时间片就执行完了。
同时从这些子进程的输出可以看出:
任务1才输出了一句话就换到了任务2的输出,说明cpu在这些进程之间发送了切换。他们的调度是由cpu决定的。
由于主进程没等待这些子进程就直接输出了,那如果想到主进程等待所以子任务结束后在执行呢?使用join()
但是使用join()之前要先把进程池关闭了。
#关闭进程池
p.close()
p.join()
我自己的理解是,如果不把进程池关闭的话,直接让主进程等待,主进程看到进程池没关闭,他就认为可能还有任务提交过来,他就一直等待。所以要关闭进程池后才能join()
apply_async与apply的时间对比
同时我们可以对比一下apply()与apply_async()的用时。
def task(num):
print('任务%s交给了进程%s处理'%(num,multiprocessing.current_process().name))
time.sleep(1)
print('任务%s结束了,进程%s执行完成'%(num,multiprocessing.current_process().name))
if __name__ == '__main__':
print('主进程开始了.........')
p = Pool(4)
#开始时间
start_time = time.time()
# 5个任务交个4个进程执行
for i in range(5):
p.apply(func=task,args=(i+1,)) #参数表示这是第几个任务
#关闭进程池
p.close()
p.join()
#到这里所有任务都结束了,计算时间差
print(f'阻塞式进程执行时间为:{time.time() - start_time}')
print('主进程结束了.........')
改为apply_async():
可以看到时间缩短了很多,所以使用上主要还是使用apply_async()方式来执行任务。