zoukankan      html  css  js  c++  java
  • day25 多进程

    day25 多进程

    今日内容

    1. 多任务
    2. 多进程
    3. 进程池

    昨日回顾

    1. 魔法方法
      1. __init__
      2. __new__
      3. __str__
      4. __len__
      5. __del__
      6. __eq__
      7. __hash__
    2. 异常处理
      1. try...except...
      2. try...except...except...
      3. try...except...else...
      4. finally

    今日内容详细

    多任务

    我们打开任务管理器,就会发现,同一时刻用很多程序在运行。这种是的计算机可以同时处理多个任务的现象就是多任务处理。

    1571133491145

    有了多任务处理,我们才能做到在听歌的同时使用QQ聊天、办公和下载文件。

    多任务处理的几个重要概念

    • 串行:程序依照先后顺序,逐个执行,一次只能执行一个任务
    • 并行:多个程序同时执行,需要CPU数目多于任务数才能实现
    • 并发:在一段时间内,多个任务一起执行,因为时间很短,看起来就像同时执行一样,程序数可以多与CPU数
    • 同步:一个程序执行完再调用另一个程序,要等程序执行完毕才能开始下一个程序
    • 异步:一个程序执行中就调用另一个程序,不需要程序执行完就可以开启下一个程序,只有异步的情况才能实现并发
    • 阻塞:CPU不工作
    • 非阻塞:CPU工作

    多进程

    程序,是一个指令的集合,也就是我们写的一套代码。

    进程则是指正在执行的程序。换句话说,当你运行一个程序,你就启动了一个进程。

    • 编写玩的代码,没有运行时,称为程序,正在运行的代码,称为进程
    • 程序是死的(静态的),继承是活的(动态的)

    操作系统轮流让各个任务交替执行。由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。

    多进程,就是让多个程序同时运行。多进程中,每个进程中所有数据(包括全局变量)都各拥有一份,互不影响。

    程序开始运行时,会首先创建一个主进程(父进程)

    在主进程下,我们可以创建新的进程,也就是子进程。

    子进程依赖于主进程,如果主进程结束,程序会退出,子进程也就自动终止。

    1571139706229

    Python提供了非常好用的多进程包multiprocessing。借助这个包,可以轻松完成单进程到并发执行的转换:

    from multiprocessing import Process
    def land_occupation(name):
        print(f'{name}占领铜锣湾')
    def grab_the_ground():
        print('抢占钵兰街')
    # Windows系统需要这行代码避免迭代导入的异常
    if __name__ == '__main__':
        print('主进程启动,帮派建立')
        haonan = Process(target=land_occupation, args=('陈浩南',), name='陈浩南占地盘的子进程')
        # target表示调用的方法,args表示调用方法的位置参数元组
        # 需要注意的是,如果元组只有一个元素,括号中需要加一个逗号
        shisanmei = Process(target=grab_the_ground, name='十三妹抢地盘的子进程')
        print(haonan.name, shisanmei.name)
        print(haonan.pid, shisanmei.pid)
        haonan.start()
        shisanmei.start()
        print(haonan.pid, shisanmei.pid)
        haonan.join()
        shisanmei.join()
        print('程序结束')
        
    输出的结果为:
    主进程启动,帮派建立
    陈浩南占地盘的子进程 十三妹抢地盘的子进程
    None None
    19932 19160
    抢占钵兰街
    陈浩南占领铜锣湾
    程序结束
    

    在上面的代码中,我们把创建的进程和调用进程的代码都写到了if __name__ == '__main__':的子句中。

    这是因为在Windows中,子进程会自动import启动它的文件。而在import时,文件中的代码会被自动执行。当执行到创建子进程的代码时,又要重新导入自己。这就陷入了一个导入自己的死循环,然后就会报错。

    1571185261271

    为了避免报错,我们把这些代码放到if __name__ == '__main__':的子句中,这样当创建子进程时,导入代码就不会重新加载创建子进程的语句了。

    1571185830236

    不过最好还是将操作多进程的代码封装到函数中,这样会避免很多麻烦。

    Process(target, name, args)参数介绍

    • target表示调用的对象,即子进程要执行的任务
    • args表示要调用对象的位置参数组成的元组(经测试,只要是可迭代对象都可以。需要注意的是,传入的内容会被迭代运行,所以要注意避免误传参数的问题)
    • name为自己成的名字,默认为Process-1等等

    Process类常用方法

    • .start():启动进程,并调用子进程中的.run()方法
    • .run():进程启动时调用的方法,正是它去调用target参数指定的函数。
    • .terminate():(了解即可)强制终止进程,不会进行任何清理操作,进程会一直占用内存空间
    • .is_alive():如果进程仍在运行,返回True,否则返回False。用来判断进程是否还在运行
    • .join([timeout]):中近程等待子进程终止,timeout时可选的超时时间

    Process类常用属性:

    • name:当前进程实例的别名,默认为Process-N,N为从1开始递增的整数
    • pid:当前进程实例的PID值,也就是操作系统为该进程进行的编号。pid在程序就绪之前(start方法未执行)的状态时值为None。只有当进程就绪,才会被分配PID值。

    全局变量在多个进程中是不共享的。进程之间的数据相互独立,默认情况下不会相互影响:

    from multiprocessing import Process
    num = 10
    def r1():
        global num
        num += 5
        print('在进程一中,num的值为:', num)
    def r2():
        global num
        num += 10
        print('在进程二中,num的值为:', num)
        
    if __name__ == '__main__':
        p1 = Process(target=r1)
        p2 = Process(target=r2)
        p1.start()
        p2.start()
        p1.join()
        p2.join()
        print('程序结束前,主进程中num的值为:', num)
        
    输出的结果为:
    在进程一中,num的值为: 15
    在进程二中,num的值为: 20
    程序结束前,主进程中num的值为: 10
    

    需要注意的是,每次创建进程对象时,都会import当前文件。这就导致在if __name__ == '__main__'语句之后对num进行的修改操作不会被加载到进程对象的内存中。换句话说,子进程能够加载全局变量在if __name__ == '__main__'语句之外的修改,却无法加载其内部的修改:

    from multiprocessing import Process
    num = 10
    def r1():
        global num
        num += 5
        print('在进程一中,num的值为:', num)
    def r2():
        global num
        num += 10
        print('在进程二中,num的值为:', num)
    # 在外部修改全局变量num
    num += 500
    if __name__ == '__main__':
        # 在内部修改全局变量
        num += 1000
        p1 = Process(target=r1)
        p2 = Process(target=r2)
        p1.start()
        p2.start()
        p1.join()
        p2.join()
        print('程序结束前,主进程中num的值为:', num)
        
    输出的结果为:
    在进程一中,num的值为: 515
    在进程二中,num的值为: 520
    程序结束前,主进程中num的值为: 1510
    

    在子进程中,1000都没有被加上,但是500被加上了。由此可以验证前面的观点。

    除了直接使用Process创建类对象之外,我们还可以自己写进程对象,只需继承Process类即可:

    from multiprocessing import Process
    import time
    class  ClockProcess(Process):
        # 重写的run方法就是我们要执行的子进程方法
        def run(self):
            n = 5
            while n > 0:
                print(n)
                time.sleep(1)
                n -= 1
    if __name__ = '__main__':
        p = ClockProcess()
        p.start()
        p.join()
    
    输出的结果为:
    5
    4
    3
    2
    1
    上面的内容每隔一秒钟打印出一个
    

    进程池

    进程池用来创建多个进程。

    当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态生成多个进程。但如果我们需要创建大量的进程,如果手动创建那工作量将极其巨大。而且会产生很多的重复代码。此时,就可以用到multiprocessing模块提供的Pool来实现批量创建多个进程。

    初始化Pool时,可以指定一个最大进程数。当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行。

    创建进程池的基本办法为:

    from multiprocessing import Pool
    import time
    
    def r1():
        print('这里是进程一呀~')
        time.sleep(5)
    def r2():
        print('这里是进程二呀~')
        time.sleep(3)
    if __name__ == '__main__':
        # 定义一个进程池,参数为最大进程数,默认大小为CPU核数
        po = Pool()
        for i in range(100):
            # apply_async选择要调用的目标,每次循环会用空出来的子进程去调用目标
            po.apply_async(r1)
            po.apply_async(r2)
        # 进程池关闭后不再接收新的请求
        po.close()
        # 等待po中所有子进程结束,必须放在close后面
        po.join()
    # 在多进程中,主进程一般用来等待,真正的任务都在子进程中
    

    multiprocessing.Pool常用函数解析

    • apply_async(func[, args[, kwargs]]):使用非阻塞方式调用func(并行执行,阻塞方式必须等待上一个进程退出才能执行下一个进程)。args为传递给func的位置参数,kwargs为传递给func 的关键字参数
    • apply(func[, args[, kwargs]])(了解即可)使用阻塞方式调用func,效果与单进程相同
    • close():关闭进程池,使其不再接收新的任务
    • join():主进程阻塞,等待子进程的退出,必须在close或terminate之后使用
  • 相关阅读:
    1.1 git和github -1 介绍
    seajs使用
    seajs使用-1 解决冲突和依赖
    6. 菜单切换
    5. 背景半透明 元素不透明(兼容所有浏览器)
    4. 父元素如何包含子元素
    3. js 多维数组转为一维数组
    2. 伪数组转为数组 Array.prototype.slice.call(arguments)
    3. 伪元素清除最后一个border的边框
    2. 详解 CSS 属性
  • 原文地址:https://www.cnblogs.com/shuoliuchina/p/11681073.html
Copyright © 2011-2022 走看看