美文网首页
Python多进程中的multiprocessing

Python多进程中的multiprocessing

作者: 蜀山客e | 来源:发表于2020-12-09 13:22 被阅读0次

    一、多任务的引入

    在现实生活中,有很多的场景中的事情是同时进行的,比如开车的时候,手和脚共同来驾驶汽车;再比如,唱歌跳舞也是同时进行的。

    下来,我们在程序里面,模拟一下“唱歌跳舞”这件事情,如下:

    from time import sleep
    
    def sing():
        for i in range(3):
            print("正在唱歌...%d"%i)
            sleep(1)
    
    def dance():
        for i in range(3):
            print("正在跳舞...%d"%i)
            sleep(1)
    
    if __name__ == '__main__':
        sing() #唱歌
        dance() #跳舞
    
    

    注意:

    1. 很显然刚刚的程序并没有完成唱歌和跳舞同时进行的要求。
    2. 如果想要实现“唱歌跳舞”同时进行,那么就需要一个新的方法,叫做:多任务。

    二、多任务的概念

    什么叫“多任务”呢?简单地说,就是操作系统可以同时运行多个任务。打个比方,我们一边在用浏览器上网,一边在听MP3,一边在用Word写作业,这就是多任务,至少同时有3个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。

    现在,多核CPU已经非常普及了。但是,即使过去的单核CPU,也可以执行多任务。由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?

    答案就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。

    真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。

    实现多任务的策略(调度算法):

    1. 时间片轮转
    2. 优先级调度

    实现多任务的三种方式:

    1. 进程
    2. 线程
    3. 协程

    多任务的原理:

    1. 并发:假的多任务,时间片的轮转,快速的交替运行任务。
    2. 并行:真的多任务,一个核处理一个任务。

    三、进程和程序的区别

    编写完毕的代码,在没有运行的时候,称之为程序。正在运行着的代码,就成为进程。

    进程,除了包含代码以外,还有需要运行的环境等,所以和程序是有区别的。

    四、multiprocessing

    如果你打算编写多进程的服务程序,Unix/Linux无疑是正确的选择。由于Windows没有fork调用,难道在Windows上无法用Python编写多进程的程序?

    由于Python是跨平台的,自然也应该提供一个跨平台的多进程支持。multiprocessing模块就是跨平台版本的多进程模块。

    multiprocessing模块提供了一个Process类来代表一个进程对象,下面的例子演示了启动一个子进程并等待其结束:

    from multiprocessing import Process
    import os
    
    # 子进程要执行的代码
    def run_proc(name):
        print('子进程运行中,name= %s ,pid=%d...' % (name, os.getpid()))
    
    if __name__ == '__main__':
        print('父进程 %d.' % os.getppid())
        p = Process(target=run_proc, args=('test',))
        print('子进程将要执行')
        p.start()
        p.join()
        print('子进程已结束')
    
    

    说明:

    1. 创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动,这样创建进程比fork()还要简单。
    2. join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。

    五、Process语法结构

    Process([group [, target [, name [, args [, kwargs]]]]])
    
    
    • target:表示这个进程实例所调用对象;
    • args:表示调用对象的位置参数元组;
    • kwargs:表示调用对象的关键字参数字典;
    • name:为当前进程实例的别名;
    • group:大多数情况下用不到;

    Process类常用方法:

    • is_alive():判断进程实例是否还在执行;
    • join([timeout]):是否等待进程实例执行结束,或等待多少秒;
    • start():启动进程实例(创建子进程);
    • run():如果没有给定target参数,对这个对象调用start()方法时,就将执行对象中的run()方法;
    • terminate():不管任务是否完成,立即终止;

    Process类常用属性:

    • name:当前进程实例别名,默认为Process-N,N为从1开始递增的整数;
    • pid:当前进程实例的PID值;
    from multiprocessing import Process
    import os
    from time import sleep
    
    # 子进程要执行的代码
    def run_proc(name, age, **kwargs):
        for i in range(10):
            print('子进程运行中,name= %s,age=%d ,pid=%d...' % (name, age,os.getpid()))
            print(kwargs)
            sleep(0.5)
    
    if __name__ == '__main__':
        print('父进程 %d.' % os.getppid())
        p = Process(target=run_proc, args=('test',18), kwargs={"m":20})
        print('子进程将要执行')
        p.start()
        sleep(1)
        p.terminate()
        p.join()
        print('子进程已结束')
    
    

    进程编号的作用:

    • 进程的编号的目的是验证主进程和子进程的关系,可以得知子进程是由哪个主进程创建出来的。

    获取进程编号的方式:

    1. 获取主(父)进程pid:os.getppid()
    2. 获取子进程pid:os.getpid()
    • 所有的子进程都来自于父进程,因此一个程序中的主进程编号得知,子进程编号按照主进程编号为起始值加一计算。
    • multiprocessing.current_process()方法获取当前的当前进程的详细信息(进程名称和进程编号)。

    multiprocessing.current_process():获取当前的当前进程的详细信息(进程名称和进程编号)。

    from multiprocessing import Process
    import time
    import os
    
    #两个子进程将会调用的两个方法
    def  worker_1(interval):
        print("worker_1,父进程(%s),当前进程(%s)"%(os.getppid(),os.getpid()))
        t_start = time.time()
        time.sleep(interval) #程序将会被挂起interval秒
        t_end = time.time()
        print("worker_1,执行时间为'%0.2f'秒"%(t_end - t_start))
    
    def  worker_2(interval):
        print("worker_2,父进程(%s),当前进程(%s)"%(os.getppid(),os.getpid()))
        t_start = time.time()
        time.sleep(interval)
        t_end = time.time()
        print("worker_2,执行时间为'%0.2f'秒"%(t_end - t_start))
    
    #输出当前程序的ID
    print("进程ID:%s"%os.getpid())
    
    #创建两个进程对象,target指向这个进程对象要执行的对象名称,
    #args后面的元组中,是要传递给worker_1方法的参数,
    #因为worker_1方法就一个interval参数,这里传递一个整数2给它,
    #如果不指定name参数,默认的进程对象名称为Process-N,N为一个递增的整数
    p1=Process(target=worker_1,args=(2,))
    p2=Process(target=worker_2,name="dongGe",args=(1,))
    
    #使用"进程对象名称.start()"来创建并执行一个子进程,
    #这两个进程对象在start后,就会分别去执行worker_1和worker_2方法中的内容
    p1.start()
    p2.start()
    
    #同时父进程仍然往下执行,如果p2进程还在执行,将会返回True
    print("p2.is_alive=%s"%p2.is_alive())
    
    #输出p1和p2进程的别名和pid
    print("p1.name=%s"%p1.name)
    print("p1.pid=%s"%p1.pid)
    print("p2.name=%s"%p2.name)
    print("p2.pid=%s"%p2.pid)
    
    #join括号中不携带参数,表示父进程在这个位置要等待p1进程执行完成后,
    #再继续执行下面的语句,一般用于进程间的数据同步,如果不写这一句,
    #下面的is_alive判断将会是True,在shell(cmd)里面调用这个程序时
    #可以完整的看到这个过程,大家可以尝试着将下面的这条语句改成p1.join(1),
    #因为p2需要2秒以上才可能执行完成,父进程等待1秒很可能不能让p1完全执行完成,
    #所以下面的print会输出True,即p1仍然在执行
    p1.join()
    print("p1.is_alive=%s"%p1.is_alive())
    
    

    六、进程的创建-Process子类

    创建新的进程还能够使用类的方式,可以自定义一个类,继承Process类,每次实例化这个类的时候,就等同于实例化一个进程对象,请看下面的实例:

    from multiprocessing import Process
    import time
    import os
    
    #继承Process类
    class Process_Class(Process):
        #因为Process类本身也有__init__方法,这个子类相当于重写了这个方法,
        #但这样就会带来一个问题,我们并没有完全的初始化一个Process类,所以就不能使用从这个类继承的一些方法和属性,
        #最好的方法就是将继承类本身传递给Process.__init__方法,完成这些初始化操作
        def __init__(self,interval):
            Process.__init__(self)
            self.interval = interval
    
        #重写了Process类的run()方法
        def run(self):
            print("子进程(%s) 开始执行,父进程为(%s)"%(os.getpid(),os.getppid()))
            t_start = time.time()
            time.sleep(self.interval)
            t_stop = time.time()
            print("(%s)执行结束,耗时%0.2f秒"%(os.getpid(),t_stop-t_start))
    
    if __name__=="__main__":
        t_start = time.time()
        print("当前程序进程(%s)"%os.getpid())
        p1 = Process_Class(2)
        #对一个不包含target属性的Process类执行start()方法,就会运行这个类中的run()方法,所以这里会执行p1.run()
        p1.start()
        p1.join()
        t_stop = time.time()
        print("(%s)执行结束,耗时%0.2f"%(os.getpid(),t_stop-t_start))
    
    

    七、Process中kill的方法

    在python语言中,我们可以使用os模块中的kill方法,根据pid杀死相应进程。

    语法:os.kill(进程编号, 信号编号)

    八、进程的特性

    进程的特性介绍:

    1. 进程之间不共享全局变量
    2. 主进程会等待所有的子进程结束之后再结束

    8.1 不共享全局变量

    当一个进程对全局变量进行数据的修改,对于其他进程而言不会造成任何的影响,可以理解为每个进程都拿的是最初始的全局变量。或者可以理解为全局变量就是所谓资源,当创建一个进程,则系统会直接给这个进程里面复制一个全局变量。针对于这个全局变量而言,在进程之间都是相互独立存在的,之间没有任何的联系。


    三个进程分别操作的都是自己进程内部的全局变量test_list,不会对其他的进程里面的全局变量造成影响,所以进程之间不共享全局变量。他们的关系只有一点,不同进程之间的全局变量的名字相同而已。

    8.2 所有子进程结束主进程才会结束

    主进程会等待所有的子进程执行结束之后才能结束

    在主进程结束之前,手动结束了所有的子进程,那么程序的结束由主进程的结束来控制

    如果需要实现主进程结束则整个程序结束:

    1. 在主进程结束之前,保证所有子进程结束使用子进程的terminate()
    2. 在子进程开启之前,设置当前子进程被被主进程守护,子进程的deamon属性设为true则意味着这个子进程被主进程守护,主进程结束守护结束,子进程也结束

    九、单进程与多线程的优劣

    9.1 单进程

    默认程序运行创建一个进程。

    一个Python文件运行,就是开启一个进程去处理。

    进程中的场景:主线程去执行代码。

    9.2 多进程

    一个Python文件运行,占用一个进程去处理,假如同时要运行第二个Python文件,同样给第二个Python文件开启一个进程去处理。

    多进程可以完成多任务,每个进程就好比一个独立车间,每个车间都各自在运营,每个进程也是各自在运行,执行各自的任务。

    9.3 优劣对比

    1. 单进程开发简单;多线程开发复杂;
    2. 单进程在处理高并发时一般采用多启动进程的方式;多线程仅需启动多个线程。进程的切换开销比线程大;
    3. 多进程之间如果有信息通信则相对多线程效率较低,因为多线程属于同一地址空间的访问,效率相对较高(暂不涉及锁等一致性策略的影响);
    4. 多进程稳定好,一个进程死了不影响其他进程;多线程中,任意一个出现问题,将影响到所有。
    希望本文对你有所帮助~~如果对接口测试、自动化测试、面试经验交流感兴趣可以加入我们。642830685,免费领取最新软件测试大厂面试资料和Python自动化、接口、框架搭建学习资料!技术大牛解惑答疑,同行一起交流。

    相关文章

      网友评论

          本文标题:Python多进程中的multiprocessing

          本文链接:https://www.haomeiwen.com/subject/jbtiwktx.html