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时,文件中的代码会被自动执行。当执行到创建子进程的代码时,又要重新导入自己。这就陷入了一个导入自己的死循环,然后就会报错。

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

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

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之后使用

猜你喜欢

转载自www.cnblogs.com/shuoliuchina/p/11681073.html