• day 36(多进程)


    多进程

    一、multiprocessing模块

    ​ python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。Python提供了multiprocessing。
    ​ multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。
    ​ multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。

    ​ 需要再次强调的一点是:与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内。

    1、 Process类的介绍

    1.1 创建进程的类:

    Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)
    强调:

    1. 需要使用关键字的方式来指定参数
    2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号

    1.2 参数介绍:

    • group参数未使用,值始终为None
    • target表示调用对象,即子进程要执行的任务
    • args表示调用对象的位置参数元组,args=(1,2,'egon',)
    • kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}
    • name为子进程的名称

    1.3 属性介绍:

    1 p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置

    2 p.name:进程的名称

    3 p.pid:进程的pid

    4 p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)

    5 p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)

    注意:在windows中Process()必须放到# if name == 'main':下

    Since Windows has no fork, the multiprocessing module starts a new Python process and imports the calling module.
    If Process() gets called upon import, then this sets off an infinite succession of new processes (or until your machine runs out of resources).
    This is the reason for hiding calls to Process() inside

    if name == "main"
    since statements inside this if-statement will not get called upon import.
    由于Windows没有fork,多处理模块启动一个新的Python进程并导入调用模块。
    如果在导入时调用Process(),那么这将启动无限继承的新进程(或直到机器耗尽资源)。
    这是隐藏对Process()内部调用的原,使用if name == “__main __”,这个if语句中的语句将不会在导入时被调用。

    2、创建子进程的两种方式

    第一种

    from multiprocessing import Process
    import time
    
    def task(n):
        print('子进程开始')
        time.sleep(n)
        print('子进程结束')
    
    if __name__ == '__main__':
        p = Process(target=task,args=(1,))
        p.start()  # p.start 只是向操作系统发了一个信号,请求开启子进程,操作系统具体什么时候开,开多长时间你控制不了。
        time.sleep(5) #
        print('我是主进程')
    

    第二种

    from multiprocessing import Process
    import time
    
    
    class MyProcesszzz(Process):
        def __init__(self,x):# 如果不传参没必要重写init
            super().__init__()
            self.n = x
    
        def run(self):
            print('我是子进程 start')
            time.sleep(self.n)
            print('我是子进程 end')
    
    
    if __name__ == '__main__':
        p1 = MyProcesszzz(2)
        p1.start()
    
        print('我是主进程')
    
    
    '''
    思考:主进程是什么结束的呢?
    ps: 主进程会在子进程都结束的时候再结束 
    why?详情见僵尸进程
    '''
    

    3、 僵尸进程孤儿进程

    '''引号的内容不适合初次阅读本博客思考
    # 僵尸进程
    1 何为没死干净的进程--》2 父亲都会有什么需求--》3 僵尸进程的概念--》4 什么时候回收pid合理--》5 之所以父等待子进程结束的原因。
    # 孤儿进程
    1 父进程死了谁来回收自己程? --》2 孤儿进程的概念 --》是父进程来回收好?还是init回收好?--》孤儿进程总归是无害的。
    '''
    
    #二:僵尸进程(父进程一直不死不停造子进程并且不回收僵尸进程有害)
        一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。
    因此,UNⅨ提供了一种机制可以保证父进程可以在任意时刻获取子进程结束时的状态信息:
    1、在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)
    2、直到父进程通过wait / waitpid来取时才释放. 但这样就导致了问题,如果进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
    
      任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。  如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。
    # 二:孤儿进程(无害)
      孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
    
      孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。
    

    可能出现的情况:

    情况一

    1 父进程正常回收子进程 (无论父进程结束自动回收和手动回收僵尸进程均无害)

    情况二

    2 父进程先死了,出现了孤儿进程(可能父进程先死了 ,子进程没有死 ,子进程编程了孤儿进程由init管理回收,无害。)

    情况三

    3 父进程一直不死不断开启子进程,并且不手动回收。(有害)

    父进程一直不死不断开启子进程,一直不发起回收僵尸进程的情况,父进程不死,init不会帮父进程回收,产生了大量的僵尸进程无人回收,占用了大量的pid,会导致其他程序无pid可用。

    如何解决情况三 ?

    直接杀死父进程,所有的子进程均变为子进程均变为孤儿进程,统一由init回收。

    4、证明进程间内存在空间隔离

    # 一个进程修改代码,肯定生效
    x = 0
    def task():
        global x
        x = 50
        print(x)
    
    task()
    print(x)
    
    from multiprocessing import Process
    # 一个进程修改代码,肯定生效
    x = 0
    def task():
        global x # 子进程修改的是自己的名称空间里的x,与主进程无关。
        x = 50
        print(f'子进程的x:{x}') 
    if __name__ == '__main__':    
        p = Process(target=task)
        p.start()
        print(f'主进程的x:{x}')
    

    5、Process的join用法

    5.1 join具体用法

    from multiprocessing import Process
    import time
    def task():
        print('子进程 开始')
        time.sleep(5)
        print('子进程 结束')
    if __name__ == '__main__':
        start_time = time.time() # 记录开始时间
        p = Process(target=task)
        p.start()
        p.join() # 如遇join会阻塞该子进程,直到该子进程结束。并且join调用了wait方法释放僵尸进程。
        end_time = time.time() # 记录结束时间
        print(end_time-start_time) # 计算时间差
        print('主线程')
        
        '''输出:
        
        子进程 开始
        子进程 结束
        5.051951885223389
        
        主线程
        '''
    

    5.2 思考

    如果有多个join是否是串行了?

    from multiprocessing import Process
    import time
    def task(n):
        print('子进程 开始')
        time.sleep(n)
        print('子进程 结束')
    if __name__ == '__main__':
        start_time = time.time() # 记录开始时间
        p1 = Process(target=task,args=(2,))
        p2 = Process(target=task,args=(4,))
        p3 = Process(target=task,args=(6,))
    
        p1.start()
        p2.start()
        p3.start()
        # 换顺序也是一样的也是按照时长最长的那个计算。
        p1.join() # 等待2s
        p2.join() # 等待2s
        p3.join() # 等待2s
        end_time = time.time() # 记录结束时间
        print(end_time-start_time) # 计算时间差
        print('主线程')
    
    
    
        '''输出:
        
        子进程 开始
        子进程 开始
        子进程 开始
        子进程 结束
        子进程 结束
        子进程 结束
        7.566513776779175
        主线程
        
        '''
    

    5.3 join串行的情况

    from multiprocessing import Process
    import time
    def task(n):
        print('子进程 开始')
        time.sleep(n)
        print('子进程 结束')
    if __name__ == '__main__':
        start_time = time.time() # 记录开始时间
        p1 = Process(target=task,args=(2,))
        p2 = Process(target=task,args=(4,))
        p3 = Process(target=task,args=(6,))
    
        p1.start()
        p1.join() 
        p2.start()
        p2.join() 
        p3.start()
        p3.join() 
        end_time = time.time() # 记录结束时间
        print(end_time-start_time) # 计算时间差
        print('主线程')
    
        '''输出:
        
        子进程 开始
        子进程 结束
        子进程 开始
        子进程 结束
        子进程 开始
        子进程 结束
        15.407774925231934
        主线程
        
        '''
        # ps:反而不如不开进程,正常调用三次来的快。
    

    5.4 精炼代码

    from multiprocessing import Process
    import time
    def task(n):
        print('子进程 开始')
        time.sleep(n)
        print('子进程 结束')
    if __name__ == '__main__':
        start_time = time.time() # 记录开始时间
        task_list = []
        for i in range(1,4):
            p = Process(target=task,args=(i,))
            p.start()
            task_list.append(p)
        print(task_list) # [<Process(Process-1, started)>, <Process(Process-2, started)>, <Process(Process-3, started)>]
        for i in task_list:
            i.join()
        end_time = time.time() # 记录结束时间
        print(end_time-start_time) # 计算时间差 4.764175891876221
        print('主线程')
    
    

    6、Process的其他用法(了解)

    6.1 pid用法

    '''
        在当前进程查看当前进程pid
            os.getpid()
            current_process().pid
        在当前进程查看子进程pid
            子进程对象.pid()
        在当前进程查看父进程pid
            os.getppid()   
    '''
    
    from multiprocessing import Process,current_process
    import time,os
    def task(x):
        print('进程开始')
        print('子进程对象的pid:',os.getpid()) # 在子进程中查看自己的pid
        print('子进程对象的pid:',current_process().pid) # 在子进程中查看自己的pid
        print('子进程对象的父进程对象的pid:',os.getppid()) # 查看父进程对象的pid
        time.sleep(100) # 注意要保证子进程不死,方便在cmd下测试。
        print('进程结束')
    
    if __name__ == '__main__':
        p = Process(target=task,args=('子进程',))
        p.start()
        print('在父进程中查看子进程的pid',p.pid) # 在父进程中查看子进程的pid
        print('主进程自己查看自己的pid:',os.getpid()) # 父进程查看自己的pid
        print('主进程自己查看自己的pid:',current_process().pid) # 父进程查看自己的pid
    
    

    6.2 name用法

    from multiprocessing import Process,current_process
    
    def task():
        print(current_process().name) # 在子进程查看自己的name
    
    if __name__ == '__main__':
        p = Process(target=task) 
        p2 = Process(target=task)
        p3 = Process(target=task,name='rocky') # 已定义name属性为rocky
        p.start()
        p2.start()
        p3.start()
        print(p.name) #Process-1
        print(p2.name) #Process-2
        print(p3.name) #rocky
    
    

    6.3 is_alive用法

    from multiprocessing import Process,current_process
    import time
    def task():
        print('子进程开始')
        time.sleep(1)
        print('子进程结束')
    
    if __name__ == '__main__':
        p = Process(target=task)
        p.start()
        # p.terminate() #发送一个指令给操作系统但是不会立即结束子进程
        # time.sleep(1)
        print(p.is_alive()) # True 判断子进程代码是否结束
        time.sleep(3)
        print(p.is_alive()) # False
    
    

    7、守护进程

    主进程创建守护进程

      其一:守护进程会在主进程代码执行结束后就终止

      其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children

    7.1 例一

    主进程代码运行完,守护进程立即结束

    from multiprocessing import Process
    import time
    def foo():
        print('守护进程开始')
        time.sleep(5)
        print('守护进程开始')
    
    
    if __name__ == '__main__':
        p1 = Process(target=foo)
        p1.daemon = True # 一定要凡在start之前,表示设置为一个守护进程。
        p1.start()
        print('主进程')
        
        '''输出:
        主进程
        '''
        # 守护进程一旦发现主进程代码运行完,立刻结束,并不会管自己的进程是否运行完
    
    

    7.2 例二

    主进程代码运行完后等在子进程运行阶段,线程不会参与守护

    from multiprocessing import Process
    
    import time
    def foo():
        print('守护进程开始')
        time.sleep(5)
        print('守护进程结束')
    def task():
        print('子进程开始')
        time.sleep(3)
        print('子进程开始')
    
    if __name__ == '__main__':
        p1 = Process(target=foo)
        p2 = Process(target=task)
        p1.daemon = True # 一定要凡在start 之前
        p1.start()
        p2.start()
        # p2 = Process(target=task)
        print('主进程')
    
        '''输出:
        主进程
        子进程开始
        子进程开始
        '''
        #分析 守护进程在运行完主进程最后一行代码就结束,但是主进程并没有结束,主进程在等待子进程运行结束.
    
    

    7.3 什么情况适合用守护进程?

    ​ 当主进程代码结束,该子进程再执行无意义的情况可以用守护进程。

  • 相关阅读:
    python自动化测试基础篇no.1
    (易忘篇)java8新特性
    (易忘篇)java基础编程高级阶段5
    (易忘篇)java基础编程高级阶段4
    (易忘篇)java基础编程高级阶段2
    (易忘篇)java基础编程高级阶段1
    (易忘篇)java基础编程难点5
    (易忘篇)java基础编程难点4
    (易忘篇)java基础编程难点3
    (易忘篇)java基础编程难点2
  • 原文地址:https://www.cnblogs.com/mgytz/p/11570374.html
Copyright © 2020-2023  润新知