• python入门(十九讲):多进程


    1、进程概念

    进程(Process)是计算机中的程序关于某数据集合上的一次运行活动。是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

    狭义定义:进程是正在运行的程序的实例。

    在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;

    在当代面向线程设计的计算机结构中,进程是线程的容器。

    程序是指令、数据及其组织形式的描述,进程是程序的实体。

    2、进程的特征

    动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。

    并发性:任何进程都可以同其他进程一起并发执行。

    独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位

    异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进。

    3、进程的组成

    结构特征:进程由程序、数据和进程控制块(PCB)三部分组成。

    程序:

    多个不同的进程可以包含相同的程序

    一个程序在不同的数据集里就构成不同的进程,能得到不同的结果

    但是执行过程中,程序不能发生改变。

    数据:

    一个计算机系统进程包括(或者说‘拥有’)以下数据:

     程序的可运行机器码所在存储器的映像

     分配到的存储器(通常包括虚拟内存的一个区域)

     存储器的内容包括可运行代码、特定于进程的数据(输入、输出)

     调用堆栈、堆栈(用户保存运行时运数中途产生的数据)

     分配给该进程的资源的操作系统描述符,诸如文件描述符(Unix术语)

    或文件句柄

     当进程正在运行时,状态通常存储在寄存器,其他情况在存储器。

    CPU(运算器+寄存器)

    PCB:

    由于进程控制块中记录进程存在的特征信息,PCB与进程同生死,创建一个进程就是为其建立一个PCB,当进程被撤销时,系统就回收它的PCB,os对进程的控制要是根据PCB来进行,对进程管理也通过对PCB管理来实现,所以进程控制块是进程存在的唯一实体。

    PCB信息:

     进程标示符:它用于唯一地标识一个进程,它有外部标示符(由字母组成、供用户使用)和内部标示符(由整数组成,为方便系统管理而设置)二种。

     进程调度信息:它包括进程状态(running、ready、blacked)、队列(就绪、阻塞队列)、队列指针,调度参数:进程优先级、进程已执行时间和已等待时间等。

     处理器状态信息:它由处理机各种寄存器(通用寄存器、指令计数器、程序状态字PSW、用户栈指针等)的内容所组成,该类信息使进程被中断后重新执行时能恢复现场从断点处继续执行。

     进程控制信息:它包括程序和数据的地址、I/O资源清单、保证进程正常运行的同步和通信机制。

     家族信息:它包括该进程的父、子进程标示符、进程的用户主等。

    4、进程状态

    1)就绪状态(Ready

    2)运行状态(Running

    3)阻塞状态(Blocked

     

    三个基本状态之间可能转换和转换的原因如下:

    就绪态à运行态:当处理器空闲时,进程调度程序必将处理器分配给一个处于就绪态的进程,该进程便由就绪态转换为运行态

    运行态à阻塞态:处于运行态的进程在运行过程中需要等待某一事件发生后(例如因I/O请求等待I/O完成后),才能继续运行,则该进程放弃处理器,从进行态转为阻塞态。

    阻塞态à就绪态:处于阻塞态的进程,若其等待的事件已经发生,于是进程由阻塞态转换为就绪态

    运行态à就绪态:处于运行状态的进程在其运行过程中,因分给它的处理机时间片已用完,而不得不让出(被抢占)处理器,于是进程由运行态转换为就绪态。

    而阻塞态à运行态和就绪态à阻塞态这两种状态转换不可能发生。

    处于运行态进程:如系统有一个处理器,在任何一时刻,最多只有一个进程处于运行态。

    处于就绪态进程:一般处于就绪态的进程按照一定的算法(如先来的进程排在前面,或采取优先权高的进程排在前面)排成一个就绪队列RL

     

    处于阻塞态进程:处于阻塞态的进程排在阻塞队列中。由于等待事件原因不同,阻塞队列也按事件分成几个队列。

    问题:

    假设一个只有一个处理机的系统中,os的进程有运行、就绪、阻塞三个基本状态。假如某时刻该系统中有10个进程并发执行,在略去调度程序所占用时间情况下,试问:

    这时刻系统中处于运行态的进程数最多几个?最少几个?  1个、0

    这时刻系统中处于就绪态的进程数最多几个?最少几个?  9个、0

    这时刻系统中处于阻塞态的进程数最多几个?最少几个?  10个、0

     

    进程控制原语(create):

    一个进程可借助创建原语来创建一个新进程。该新进程是它的子进程,创建一个进程主要是为新进程创建一个PCB。创建原语首先从系统的PCB表中索取一个空白的PCB表目,并获得其内部标识,然后将调用进程提供的参数:如外部名、正文段、数据段的首址、大小、所需资源、优先级等填入这张空白PCB表中。并设置新进程状态为活动/静止就绪态,并把该PCB插入到就绪队列RQ中,就可进入系统并发执行。

    撤销原语(Destory/终止(Termination

    对于树型层次结构的进程系统撤销原语采用的策略是由父进程发出,撤销她的一个子进程及该子进程所有的子孙进程,被撤销进程的所有资源(主存、I/O资源、PCB表目)全部释放出来归还子系统,并将它们从所有的队列中移去。如撤销的进程正在执行,则要调用进程调度程序将处理器部分给其他进程。

    阻塞原语(block:

    当前进程因请求某事件而不能执行时(例如请求I/O等待I/O完成时),该进程将调用阻塞原语阻塞自己,暂时放弃处理器。进程阻塞是进程自身的主动行为。阻塞过程首先立即停止原来程序的执行,PCB中的现行状态由运行状态改为活动阻塞态,并将PCB插入到等待某事件的阻塞队列中,最后调用进程调度程序进行处理机的重新分配。

    唤醒原语(wakeup

    当被阻塞的进程锁期待的事件发生时(例如I/O完成时),则有关进程和过程(例如I/O设备处理程序或释放资源的进程等)调用wakeup原语,将阻塞的进程唤醒,将等待该事件的进程从阻塞队列移出,插入到就绪队列中,将该进程的PCB中现行状态,如是活动阻塞态改为活动就绪态,如是静止阻塞态改为静止就绪态。

    挂起原语(suspend

    调用挂起原语的进程只能挂起它自己或它的子孙,而不能挂起别的族系的进程。挂起原语的执行过程是:检查要挂起进程PCB的现行状态,若正处于活动就绪态,便将它改为静止就绪态,如是活动阻塞态则改为静止阻塞态。如是运行态,则将它改为静止就绪态,并调用进程调度程序重新分配处理器。为了方便用户或父进程考察该进程的运行情况,需把该进程的PCB复制到内存指定区域。

    激活原语(active

    用户进程或父进程通过调用激活原语将被挂起的进程激活。激活原语执行过程是:检查被挂起原语进程PCB中的现行状态,若处于静止就绪态,则将它改为活动就绪态,若处于静止阻塞态,则将它改为活动阻塞态。

     

    5、进程和线程的关系

    通常在一个进程中可以包含若干个线程,他们可以利用进程所拥有的资源,

    在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位

    而把线程作为独立运行和独立调度的基本单位,

    由于线程比进程更小,基本上不拥有系统资源,

    故对它的调度所付出的开销会小得多,

    能更高效的提高系统内多个程序间并发执行的程度。

    一个进程至少有一个线程。两个进程AB,是相互独立的,不同进程的资源是不能随意操作的。

    线程必须存在于进程中。

     

    Python注重于多进程而不注重多线程的原因是:

    Python执行的底层机制GIL:全局解释锁,不论几核,python的进程在任何时刻最多在一个CPU上执行。无法利用到多核CPU,如果利用python的多线程编程,是无法利用到多核CPU的,因为多线程是共存与同一个进程中,但是同一个进程又不能利用多核CPU,所以python的多线程编程是不能利用到多核CPU的,那么执行并发的效率和单机没什么区别,效率是挺低的。所以在python中如果想产生比较大的并发量,想利用到多核CPU的话, 一定要用到多进程编程。

    并行:两个或多个事件在同一时刻被执行

    并发:两个或多个事件在同一时刻被执行了,但是不一定在同一时刻运行。在同一时间间隔内发生。

     

    6、进程的同步

    在多道程序环境下,系统中各进程以不可预测的速度向前推进,进程的异步性会造成了结果的不可再现性。为防止这种现象,异步的进程间推进受到两种限制:

    资源共享关系:

    像打印机这种资源一次只允许一个进程使用的资源称为临界资源。属于临界资源有硬件打印机、磁带机等,软件在消息缓冲队列、变量、数组、缓冲区等。当然还有一类象磁盘等资源,它允许进程间共享,即可交替使用,所以它称为共享资源,而临界资源又称独享资源。

    多进程之间如果共享资源,例如各进程争用一台打印机,这是各进程使用这台打印机时有一定的限制。每次只允许一个进程使用一段时间打印机,等该进程使用完毕后再将打印机分配给其他进程。这种使用原则称为互斥使用。

    相互合作关系:

    在某些进程之间还存在合作关系。例如一个程序的输入、计算、打印三个程序段作为三个进程并发执行,由于这三个进程间存在着相互合作的关系,即先输入在计算、最后在打印的关系,所以这三个进程在并发执行时推进序列受到限制,要保证其合作关系正确,进程间这种关系称为同步关系。

    进程之间竞争资源也面临三个控制问题:

    l  互斥(mutual exciusion:指多个进程不能同时使用同一个资源

    l  死锁(deadlock:指多个进程互不相让,都得不到足够的资源

    l  饥饿(starvation):指一个进程一直得不到资源(其他进程可能轮流占用资源)

    进程在并发执行时为了保证结果的可再现性,各进程执行序列必须加以限制以保证互斥地使用临界资源,相互合作完成任务。多个相关进程在执行次序上的协调称为进程同步。用户保证多个进程在进行次序上的协调关系的相应机制称为进程同步机制。所有的进程同步机制应遵循下述四条准则:

    空闲让进:

    当无进程进入临界区时,相应的临界资源处于空闲状态,因而允许一个请求进入临界区的进程立即进入自己的临界区。

    忙则等待:

    当已有进程进入自己的临界区时,即相应的临界资源正被访问,因而其他试图进入临界区的进程必须等待,以保证进程互斥的访问临界资源。

    有限等待:

    对要求访问临界资源的进程,应保证进程能在有限时间进入临界区,以免陷入饥饿状态。

    让权等待:

    当进程不能进入自己的临界区时,应立即释放处理机,以免进程陷入忙等。

     

    7、fork出进程后的程序流程

    使用fork创建子进程后,子进程会复制父进程的数据信息,而后程序就分两个进程继续运行后面的程序,这也是fork(分叉)名字的含义了。在子进程内,这个方法会返回0;在父进程内,这个方法会返回子进程的编号PID。
    可以使用PID来区分两个进程

    Import os

     

    pid = os.fork()   #生成了一个子进程,出现了2个同时开始向下执行

    print{pid}

    if pid ==0:

    print(“执行子进程,子进程pid ={pid},父进程ppid ={ppid}”.format(pid=os.getpid(),ppid =os.getppid()))

    else:

    print(“执行父进程,子进程pid ={pid},父进程ppid ={ppid}”.format(pid=os.getpid(),ppid =os.getppid()))

     

    #30974    #程序自动获取函数返回值是子进程的pid

    #0        #子程序获取函数返回值是0

    #执行父进程,子进程pid =30974,父进程ppid=30973

    #执行子进程,子进程pid =30974,父进程ppid=30973

     

    os.fork()执行的时候会生成子进程,主程序还有一个主进程,此时出了2个进程,一个主进程,一个是子进程

    os.fork()规定,主程序自动获取函数返回值是子进程的pid,子程序获取函数返回值是0

    注意,在win系统中,没有fork这个系统调用

    创建两个子进程:

     

    import os,time

    from multiprocessing import Process

     

    defworker():

        print("子进程执行中>>>pid={0},ppid={1}".format(os.getpid(),os.getppid()))

        time.sleep(2)

        print("子进程终止>>> pid={0}".format(os.getpid()))

     

    defmain():

        print("主进程执行中>>>pid={0}".format(os.getpid()))

       

        ps =[]

        for i in range(2):   #创建子进程实例

            p = Process(target = worker,name ="worker"+str(i),args=())#args()主要是给work传参的,由于worker()没有参数,所以args=()

            ps.append(p)

     

        for i in range(2):    #开启进程

            ps[i].start()

     

        for i in range(2):  #阻塞进程,等所有子进程终止后,主进程在结束

            ps[i].join()

       

        print("主进程终止")

     

    if __name__=="__main__":

        main()

     

    #主进程执行中>>>pid=6980

    #子进程执行中>>>pid=5952,ppid=6980

    #子进程执行中>>>pid=8668,ppid=6980

    #子进程终止>>> pid=5952

    #子进程终止>>> pid=8668

    #主进程终止

     

     

    #如果没有阻塞进程的语句:

    #主进程执行中>>>pid=9304

    #主进程终止

    #子进程执行中>>>pid=9700,ppid=9304

    #子进程执行中>>>pid=8456,ppid=9304

    #子进程终止>>> pid=9700

    #子进程终止>>> pid=8456

    fork不是跨平台的,在win系统中不能使用,而在win系统中使用Process对象来调用子进程需要执行的方法。通过start开启子进程;在fork中,主进程不会等待子进程的结束,而结束主进程,在Process开启的子进程中,会等待所有的子进程结束后,才会结束主进程。

  • 相关阅读:
    【转】 vue的路由安全验证
    【转】 Vuex模块化
    【转】 后端小白的VUE入门笔记, 进阶篇
    【转】 后端小白的VUE入门笔记, 前端高能慎入
    【转】 一个后端开发的 Vue 笔记【入门级】
    【转】 前端笔记之Vue(七)Vue-router&axios&Vue插件&Mock.js&cookie|session&加密
    【转】 前端笔记之Vue(六)分页排序|酷表单实战&Vue-cli
    【转】 前端笔记之Vue(五)TodoList实战&拆分store&跨域&练习代理跨域
    算法题
    vue的虚拟dom(Virtual DOM )
  • 原文地址:https://www.cnblogs.com/suitcases/p/11905004.html
Copyright © 2020-2023  润新知