python 多进程的使用之(一)

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()方式来执行任务。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值