03_多进程[Python常见问题]

1.进程相关的概念

    1.同一个程序每次执行都有不同的进程,因为分配的计算机资源不同,进程由代码段,数据段,和PCB(进程控制块)组成

    2.进程的特征: 进程是操作系统资源分配的最小单位,每个进程单独占有4G的虚拟内存,进程之间相互独立,运行不受影响

    3.程序和进程的区别
        进程: 程序在计算机中一次执行的过程,占有CPU内存的计算机资源,有一定的生命周期

        程序: 是一个静态的描述,不占有计算机资源

    4.进程的状态

        1.三态: 就绪态,运行态,等待态

            就绪态: 进程具体运行条件,等待系统分配处理器运行

            运行态: 进程占有CPU处于运行的状态

            等待态: 又称阻塞态,睡眠态,进程暂时不具备运行条件,需要阻塞等待(sleep,accept),此时进程还在内存中等待获取CPU

        2.五态: 在三态上增加了新建态和终止态

            新建态: 创建一个进程,获取资源,直接表现为运行一个程序或者在程序中创建新的进程

            终止态: 进程执行结束,资源回收过程

        3.进程特殊的挂起状态: 是指各种原因进程放弃了CPU,导致进程无法继续执行,此时进程被踢出内存

        4.进程状态流程图: https://www.processon.com/view/link/5f1059f7f346fb2bfb29f2ba

    5.进程优先级

        优先级决定了一个进程的纸箱权限和占有资源的优先程度,用户程序默认优先级是0,优先级范围是 -20 ~ 19  # -20最高

        nice:

            以指定的优先级运行进程

            nice -9 ./while.py  # 以9的优先级运行此程序,以小于0的优先级运行必须加root权限

            sudo nice –9 ./while.py  # 用root权限的-9优先级运行此程序

        renice

            改变某个进程的优先级  # 改变已经在运行中的程序的优先级

            sudo renice 2 进程PID号  # 让进程以2的优先级继续运行

    6.进程的信息和保存

        PCB(进程控制块): 在Unix系统组进程创建后会在内存中开辟一块空间存放进程的相关信息,称为PCB

        PID: 在操作系统中进程的唯一标志,是大于0的整数,由系统自动分配

        ps -ajx  # 可以看到父进程的进程PID

        ps -aux  # 查看进程信息

            用户 PID 占有内存 优先级 进程状态等

            D: 等待态(不可中断等待)

            S: 等待态(可中断等待)

            T: 等待态(暂停)

            R: 运行态

            Z: 僵尸态

            +: 前台进程(不带+即为后台进程)

            <: 高优先级

            N: 低优先级

            l: 有进程链接,即有关联的进程

            s: 会话组,有一组进程实现一个大功能,会话组就相当于小组长

        top  # 动态查看当前运行的进程的状态,q退出,< >翻页

        htop  # 动态查看当前运行的进程的状态,top命令的升级版

        pstree  # 以树型结构查看进程

    7.进程的调度

        1.先来先服务

            先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度

            FCFS算法比较有利于长作业进程,而不利于短作业进程,由此可知,本算法适合于CPU繁忙型作业,而不利于I/O繁忙型的作业进程

        2.短作业优先

            短作业进程优先调度算法(SJ/PF)是指对短作业或短进程优先调度的算法,该算法既可用于作业调度,也可用于进程调度

            但其对长作业不利,不能保证紧迫性作业进程被及时处理,作业的长短只是被估算出来的

        3.时间片轮转

            1.时间片轮转(Round Robin RR)法的基本思路是让每个进程在就绪队列中的等待时间与享受服务的时间成比例,

                在时间片轮转法中,需要将CPU的处理时间分成固定大小的时间片,例如,几十毫秒至几百毫秒,

                如果一个进程在被调度选中之后用完了系统规定的时间片,但又未完成要求的任务,则它自行释放自己所占有的CPU而排到就绪队列的末尾,

                等待下一次调度,同时,进程调度程序又去调度当前就绪队列中的第一个进程

            2.因此轮转法只能用来调度分配一些可以抢占的资源,这些可以抢占的资源可以随时被剥夺,而且可以将它们再分配给别的进程,

                CPU是可抢占资源的一种,但打印机等资源是不可抢占的,由于作业调度是对除了CPU之外的所有系统硬件资源的分配,

                其中包含有不可抢占资源,所以作业调度不使用轮转法

            3.在轮转法中,时间片长度的选取非常重要,首先时间片长度的选择会直接影响到系统的开销和响应时间,

                如果时间片长度过短,则调度程序抢占处理机的次数增多,这将使进程上下文切换次数也大大增加,从而加重系统开销,

                反过来,如果时间片长度选择过长,例如一个时间片能保证就绪队列中所需执行时间最长的进程能执行完毕,则轮转法变成了先来先服务法,

                时间片长度的选择是根据系统对响应时间的要求和就绪队列中所允许最大的进程数来确定的

            4.在轮转法中,加入到就绪队列的进程有3种情况

                一种是分给它的时间片用完,但进程还未完成,回到就绪队列的末尾等待下次调度去继续执行

                另一种情况是分给该进程的时间片并未用完,只是因为请求I/O或由于进程的互斥与同步关系而被阻塞,当阻塞解除之后再回到就绪队列

                第三种情况就是新创建进程进入就绪队列

                如果对这些进程区别对待,给予不同的优先级和时间片从直观上看,可以进一步改善系统服务质量和效率

                例如:

                    我们可把就绪队列按照进程到达就绪队列的类型和进程被阻塞时的阻塞原因分成不同的就绪队列,每个队列按FCFS原则排列,

                    各队列之间的进程享有不同的优先级,但同一队列内优先级相同,

                    这样,当一个进程在执行完它的时间片之后,或从睡眠中被唤醒以及被创建之后,将进入不同的就绪队列

        4.多级反馈队列

            前3种用作进程调度的算法都有一定的局限性,如短进程优先的调度算法,仅照顾了短进程而忽略了长进程,而且如果并未指明进程的长度,

            则短进程优先和基于进程长度的抢占式调度算法都将无法使用,而多级反馈队列调度算法则不必事先知道各种进程所需的执行时间,

            而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法,在采用多级反馈队列调度算法的系统中,

            调度算法的实施过程如下所述

            1.应设置多个就绪队列,并为各个队列赋予不同的优先级,第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低,

                该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小,

                例如,第二个队列的时间片要比第一个队列的时间片长一倍, …,第i+1个队列的时间片要比第i个队列的时间片长一倍

            2.当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度,当轮到该进程执行时,如它能在该时间片内完成,

                便可准备撤离系统,如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行,

                如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列, …, 如此下去

                当一个长作业进程从第一队列依次降到第n队列后,在第n队列便采取按时间片轮转的方式运行

            3.仅当第一队列空闲时,调度程序才调度第二队列中的进程运行,仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行,

                如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列),

                则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程

2.多进程相关的概念

    1.多个进程如何占有CPU

        一个内核同一时刻只能运行一个任务,多个进程对内核资源进行争夺,最终结果根据操作系统的时间片轮转和进程优先级决定

        操作系统决来定哪个进程占用计算机核心,占有计算机核心的进程称为该进程占有CPU的时间片

    2.父子进程: 在系统除了初始化进程其它进程都有一个父进程,可能有多个子进程

    3.os.fork创建进程的流程

        1.用户空间运行程序发起进程创建申请,调用操作系统内核接口创建进程

        2.操作系统分配计算机资源,确定进程状态,将新的进程提供给用户使用

    4.multiprocessing.Process创建进程的流程

        1.需要将进程事件封装成函数,然后使用multiprocessing模块提供的Process类创建进程

        2.新的进程和对应的函数相关联,进程启动会自动执行函数完成事件,最后回收进程

    5.多进程优缺点

        优点: 并行多个任务,提高运行效率,空间独立,数据安全,创建方便

        缺点: 进程创建销毁的过程中消耗较多的计算机资源

    6.计算机开启的进程数量建议: CPU核数 + 1

3.利用os模块的fork方法实现多进程

1.os模块中进程相关函数实现多进程

        1.fork方法概述

            父进程中fork之前的内容子进程同样会复制,但父子进程空间各自独立,fork之后的修改不会影响对方

            父子进程在执行上互不影响,谁先执行谁后执行完全不确定,子进程虽然复制父进程的空间,但是有自己的特性(如PID,PCB,栈空间等)

            fork()函数实现多进程流程图: https://www.processon.com/view/link/5f10dd93f346fb2bfb2a43a0

        2.进程的创建

            pid = os.fork()  # 创建一个新的进程,无参数

            创建失败: 返回一个负数(-1)

            创建成功: 在子进程中fork的返回值0,在父进程中的返回值是大于0的正整数(新的进程PID)

        2.获取PID号

            os.getpid()  # 获取当前进程的PID号并返回

            os.getppid()  # 获取当前进程父进程的PID并返回

        3.进程的退出

            os._exit(status)  # 结束一个进程,status表示进程的结束状态,是一个整数

            sys.exit([status])  # 结束一个进程并抛出异常,传入一个正整数表示结束状态,传入字符串表示打印结束语

        5.fork方法实现多进程

import os
import time
import sys


def func1(L):
    time.sleep(5)
    L.append(33)
    print("this is %s" % sys._getframe().f_code.co_name)  # sys._getframe().f_code.co_name 取函数名
    print("my pid is %s, my list is %s" % (os.getpid(), L))


def func2(L):
    time.sleep(3)
    L.append(44)
    print("this is %s" % sys._getframe().f_code.co_name)  # sys._getframe().f_code.co_name 取函数名
    print("my pid is %s, my list is %s" % (os.getpid(), L))


def main():
    L = [11, 22]
    print("准备创建进程")
    pid = os.fork()
    if pid < 0:
        print("创建进程失败")
    elif pid == 0:
        # os.getpid()取进程pid号  os.getppid()取父进程pid号
        print("子进程中执行的代码: pid is %s 父进程的pid是: %s 调用函数func2" % (os.getpid(), os.getppid()))
        func2(L)
    else:
        # 在父进程中pid = os.fork()执行成功后的返回值pid就是子进程的pid号
        print("父进程中执行的代码: pid is %s 子进程的pid是: %s 调用函数func1" % (os.getpid(), pid))
        func1(L)
    print("父子进程都会执行的代码: my pid is %s" % os.getpid())


if __name__ == "__main__":
    main()
"""执行结果
    准备创建进程
    父进程中执行的代码: pid is 17115 子进程的pid是: 17116 调用函数func1
    子进程中执行的代码: pid is 17116 父进程的pid是: 17115 调用函数func2
    this is func2
    my pid is 17116, my list is [11, 22, 44]
    父子进程都会执行的代码: my pid is 17116
    this is func1
    my pid is 17115, my list is [11, 22, 33]
    父子进程都会执行的代码: my pid is 17115
"""
hmoban主题是根据ripro二开的主题,极致后台体验,无插件,集成会员系统
自学咖网 » 03_多进程