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聊天、办公和下载文件。并发

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

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

多进程

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

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

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

操做系统轮流让各个任务交替执行。因为CPU的执行速度实在是太快了,咱们感受就像全部任务都在同时执行同样。函数

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

程序开始运行时,会首先建立一个主进程(父进程)操作系统

在主进程下,咱们能够建立新的进程,也就是子进程。code

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

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以后使用
相关文章
相关标签/搜索