首先,思考多进程、多线程、协程的区别?
其次,什么时候该用哪种模式?
进程:一个程序在计算机里面的一次运行。
进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。一个QQ运行就是一个进程
狭义定义:进程是正在运行的程序的实例。
思考:写了一个程序还未执行,这时候它是一个什么东西?
程序:存在程序里的字符串,最终都是01,二进制
进程的特征
- 动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
- 并发性:任何进程都可以同其他进程一起并发执行
- 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
- 异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
进程的组成
结构特征:进程由程序、数据和进程控制块(PCB)三部分组成。
程序:
多个不同的进程可以包含相同的程序:
一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;
但是执行过程中,程序不能发生改变。
数据:
一个计算机系统进程包括(或者说“拥有”)下列数据:
- 程序的可运行机器码所在存储器的映像。
- 分配到的存储器(通常包括虚拟内存的一个区域)。
- 存储器的内容包括可运行代码、特定于进程的数据(输入、输出)
- 、调用堆栈、堆栈(用于保存运行时运数中途产生的数据)
- 分配给该进程的资源的操作系统描述符,诸如文件描述符(Unix术语)
- 或文件句柄(Windows)
- 当进程正在运行时,状态通常储存在寄存器,其他情况在存储器。
PCB:
由于进程控制块中记录进程存在和特性信息;PCB与进程同生死,创建一个进程就是为其建立一个PCB,当进程被撤消时,系统就回收它的PCB;OS对进程的控制要是根据PCB来进行,对进程管理也通过对PCB管理来实现,所以进程控制块是进程存在的唯一实体。
PCB的信息
- 进程标识符:它用于唯一地标识一个进程。它有外部标识符(由字母组成,供用户使用)和内部标识符(由整数组成,为方便系统管理而设置)二种。
- 进程调度信息:它包括进程状态(running、ready、blacked)、队列(就绪、阻塞队列)、队列指针,调度参数:进程优先级、进程已执行时间和已等待时间等。
- 处理机状态信息:它由处理机各种寄存器(通用寄存器、指令计数器、程序状态字PSW、用户栈指针等)的内容所组成,该类信息使进程被中断后重新执行时能恢复现场从断点处继续运行。
- 进程控制信息:它包括程序和数据的地址、I/O资源清单,保证进程正常运行的同步和通信机制等。
家族信息:它包括该进程的父、子进程标识符、进程的用户主等。
进程的上下文切换
类比:5个小学生需要使用同一块画板画画,每个学生都有画画要用的工具(纸、笔、颜料等),每个学生只能使用画板五分钟;那么这个画画要用的工具就可以理解为上下文,而第一个学生使用完五分钟之后带着自己的画画工具离开画板,下一个学生接着带自己的工具去使用画板整个过程就可以理解为进程的上下文切换。
进程上下文:
进程是由程序、数据和进程控制块组成。进程上下文实际上是执行活动全过程的静态描述。具体说,进程上下文包括系统中与执行该进程有关的各种寄存器(例如:通用寄存器、程序计数器PC、程序状态寄存器PS等)的值,程序段在经编译之后形成的机器指令代码集(或称正文段)、数据集及各种堆栈值和PCB结构。一个进程的执行是在该进程的上下文中执行,而当系统调度新进程占有处理机时,新老进程的上下发生切换。UNIX 操作系统的进程上下文称为进程映象。
进程切换:
就是从正在运行的进程中收回处理器,然后再使待运行进程来占用处理器。
这里所说的从某个进程收回处理器,实质上就是把
- 进程存放在处理器的寄存器中的中间数据找个地方存起来,
- 从而把处理器的寄存器腾出来让其他进程使用。
- 那么被中止运行进程的中间数据存在何处好呢?
- 当然这个地方应该是进程的私有堆栈。
- 让进程来占用处理器,实质上是把某个进程存放在私有堆栈中寄存器的数据
- (前一次本进程被中止时的中间数据)再恢复到处理器的寄存器中去,
- 并把待运行进程的断点送入处理器的程序指针PC,
- 于是待运行进程就开始被处理器运行了,也就是这个进程已经占有处理器的使用权了。
三个基本状态之间可能转换和转换原因如下:
- 就绪态–>运行态:当处理机空闲时,进程调度程序必将处理机分配给一个处于就绪态的进程 ,该进程便由就绪态转换为运行态。
- 运行态–>阻塞态:处于运行态的进程在运行过程中需要等待某一事件发生后(例如因I/O请求等待I/O完成后),才能继续运行,则该进程放弃处理机,从运行态转换为阻塞态。
- 阻塞态–>就绪态:处于阻塞态的进程,若其等待的事件已经发生,于是进程由阻塞态转换为就绪态。
- 运行态–>就绪态:处于运行状态的进程在其运行过程中,因分给它的处理机时间片已用完,而不得不让出(被抢占)处理机,于是进程由运行态转换为就绪态。
- 而阻塞态–>运行态和就绪态–>阻塞态这二种状态转换不可能发生。
- 处于运行态进程:如系统有一个处理机,则在任何一时刻,最多只有一个进程处于运行态。
- 处于就绪态进程:一般处于就绪态的进程按照一定的算法(如先来的进程排在前面,或采用优先权高的进程排在前面)排成一个就绪队列RL。
- 处于阻塞态进程:处于阻塞态的进程排在阻塞队列中。由于等待事件原因不同,阻塞队列也按事件分成几个队列。
解:因为系统中只有一个处理机,所以某时刻处于运行态的进程数最多只有一个。而最少可能为0,此时其它10个进程一定全部排在各阻塞队列中,在就绪队列中没有进程。而某时刻处于就绪态的进程数最多只有9个,不可能出现10个情况,因为一旦CPU有空,调度程序马上调度,当然这是在略去调度程序调度时间时考虑。处于阻塞态的进程数最少是0个。
进程状态的细化:
进程控制原语:
- 创建原语(Create)
一个进程可借助创建原语来创建一个新进程,该新进程是它的子进程,创建一个进程主要是为新进程创建一个PCB。创建原语首先从系统的PCB表中索取一个空白的PCB表目,并获得其内部标识,然后将调用进程提供的参数:如外部名、正文段、数据段的首址、大小、所需资源、优先级等填入这张空白PCB表目中。并设置新进程状态为活动/静止就绪态,并把该PCB插入到就绪队列RQ中,就可进入系统并发执行。
- 撤消原语(Destroy)/ 终止(Termination)
对于树型层次结构的进程系统撤消原语采用的策略是由父进程发出,撤消它的一个子进程及该子进程所有的子孙进程,被撤消进程的所有资源(主存、I/O资源、PCB表目)全部释放出来归还系统,并将它们从所有的队列中移去。如撤消的进程正在运行,则要调用进程调度程序将处理器分给其它进程。
- 阻塞原语(block)
当前进程因请求某事件而不能执行时(例如请求I/O而等待I/O完成时),该进程将调用阻塞原语阻塞自己,暂时放弃处理机。进程阻塞是进程自身的主动行为。阻塞过程首先立即仃止原来程序的执行,把PCB中的现行状态由运行态改为活动阻塞态,并将PCB插入到等待某事件的阻塞队列中,最后调用进程调度程序进行处理机的重新分配。
- 唤醒原语(wakeup)
当被阻塞的进程所期待的事件发生时(例如I/O完成时),则有关进程和过程(例如I/O设备处理程序或释放资源的进程等)调用wakeup原语,将阻塞的进程唤醒,将等待该事件的进程从阻塞队列移出,插入到就绪队列中,将该进程的PCB中现行状态,如是活动阻塞态改为活动就绪态,如是静止阻塞态改为静止就绪态。
- 挂起原语(suspend)
调用挂起原语的进程只能挂起它自己或它的子孙,而不能挂起别的族系的进程。挂起原语的执行过程是:检查要挂起进程PCB的现行状态,若正处于活动就绪态,便将它改为静止就绪态;如是活动阻塞态则改为静止阻塞态。如是运行态,则将它改为静止就绪态,并调用进程调度程序重新分配处理机。为了方便用户或父进程考察该进程的运行情况,需把该进程的PCB复制到内存指定区域。
- 激活原语(active)
用户进程或父进程通过调用激活原语将被挂起的进程激活。激活原语执行过程是:检查被挂起进程PCB中的现行状态,若处于静止就绪态,则将它改为活动就绪态,若处于静止阻塞态,则将它改为活动阻塞态。
老张爱喝茶,废话不说,煮开水。
出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。
1 老张把水壶放到火上,立等水开。(同步阻塞)
老张觉得自己有点傻
2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞)
老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀
~~~~的噪音。
3 老张把响水壶放到火上,立等水开。(异步阻塞)
老张觉得这样傻等意义不大
4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非
阻塞)
老张觉得自己聪明了。
所谓同步异步,只是对于水壶而言。
普通水壶,同步;响水壶,异步。
虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。
同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。
所谓阻塞非阻塞,仅仅对于老张而言。
立等的老张,阻塞;看电视的老张,非阻塞。
情况1和情况3中老张就是阻塞的,媳妇喊他都不知道。虽然3中响水壶是异步的,可对于立等的
老张没有太大的意义。所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。
通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源,
在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,
而把线程作为独立运行和独立调度的基本单位,
由于线程比进程更小,基本上不拥有系统资源,
故对它的调度所付出的开销就会小得多,
能更高效的提高系统内多个程序间并发执行的程度。
进程的同步:
在多道程序环境下,系统中各进程以不可预测的速度向前推进,进程的异步性会造成了结果的不可再现性。为防止这种现象,异步的进程间推进受到二种限制:
资源共享关系
像打印机这类资源一次只允许一个进程使用的资源称为临界资源。属于临界资源有硬件打印机、磁带机等,软件在消息缓冲队列、变量、数组、缓冲区等。当然还有一类象磁盘等资源,它允许进程间共享,即可交替使用,所以它称为共享资源,而临界资源又称独享资源。
多进程之间如果共享资源,例如各进程争用一台打印机,这时各进程使用这台打印机时有一定的限制。每次只允许一个进程使用一段时间打印机,等该进程使用完毕后再将打印机分配给其它进程。这种使用原则称为互斥使用。
相互合作关系
在某些进程之间还存在合作关系,例如一个程序的输入、计算、打印三个程序段作为三个进程并发执行,由于这三个进程间存在着相互合作的关系,即先输入再计算、最后再打印的关系,所以这三个进程在并发执行时推进序列受到限制,要保证其合作关系正确,进程间这种关系称为同步关系。
进程之间竞争资源也面临三个控制问题:
- 互斥( mutual exclusion )指多个进程不能同时使用同一个资源;
- 死锁( deadlock )指多个进程互不相让,都得不到足够的资源;
- 饥饿( starvation )指一个进程一直得不到资源(其他进程可能轮流占用资源)。
进程在并发执行时为了保证结果的可再现性,各进程执行序列必须加以限制以保证互斥地使用临界资源,相互合作完成任务。多个相关进程在执行次序上的协调称为进程同步。用于保证多个进程在执行次序上的协调关系的相应机制称为进程同步机制。所有的进程同步机制应遵循下述四条准则:
空闲让进
当无进程进入临界区时,相应的临界资源处于空闲状态,因而允许一个请求进入临界区的进程立即进入自己的临界区。
忙则等待
当已有进程进入自己的临界区时,即相应的临界资源正被访问,因而其它试图进入临界区的进程必须等待,以保证进程互斥地访问临界资源。
有限等待
对要求访问临界资源的进程,应保证进程能在有限时间进入临界区,以免陷入“饥饿”状态。
让权等待
当进程不能进入自己的临界区时,应立即释放处理机,以免进程陷入忙等。
Linux 下通过fork生成进程
""" pid=os.fork() 1.只用在Unix系统中有效,Windows系统中无效 2.fork函数调用一次,返回两次: 在父进程中返回值为子进程id,在子进程中返回值为0 """ import os pid=os.fork() #生成了一个子进程,出现了2个进程同时开始向下执行: if pid==0: print("执行子进程,子进程pid={pid},父进程ppid={ppid}".\ format(pid=os.getpid(),ppid=os.getppid())) else: print("执行父进程,子进程pid={pid},父进程ppid={ppid}".\ format(pid=pid,ppid=os.getpid()))
解析:
os.fork() 执行的时候会生成子进程,主程序还有一个主进程,
此时出了2个进程,一个主进程,一个是子进程
os.fork()规定,主进程自动获取函数返回值是子进程的pid
子进程获取函数返回值是0
主进程:pid=19293
if pid==0:
print("执行子进程,子进程pid={pid},父进程ppid={ppid}".\
format(pid=os.getpid(),ppid=os.getppid()))
else:
print("执行父进程,子进程pid={pid},父进程ppid={ppid}".\
format(pid=pid,ppid=os.getpid()))
主进程执行的结果:else的分支
子进程:pid = 0
if pid==0:
print("执行子进程,子进程pid={pid},父进程ppid={ppid}".\
format(pid=os.getpid(),ppid=os.getppid()))
else:
print("执行父进程,子进程pid={pid},父进程ppid={ppid}".\
format(pid=pid,ppid=os.getpid()))
子进程执行的结果:
if的分支
综合起来看,因为2个进程分别执行if和else的代码块,所以最后的结果是,if被子进程执行了一次,else被主进程执行了一次