文章目录
- 1. 进程、线程、协程
- 1.1 进程
- 1.1.1 进程间的通信方式
- 1.1.2 进程同步方式
- 1.1.3 进程的调度算法
- 1.1.4 优先级反转
- 1.1.5 进程状态
- 1.1.6 PCB进程控制块
- 1.1.7 进程的创建和撤销过程
- 1.1.8 为什么要有进程
- 1.2 线程
- 1.2.1 为什么要有线程
- 1.2.2 线程间的同步方式
- 1.3 协程
- 1.3.1 什么是协程
- 1.3.2 为什么引入协程
- 1.3.3 协程的特点
- 1.3.4 协程与线程的区别
- 1.4 进程和线程的区别
1. 进程、线程、协程
1.1 进程
进程是操作系统进程资源分配的基本单位。
进程控制块(PCB,Process Control Block)描述进程的基本信息和运行状态。创建进程和撤销进程都是对PCB的操作。
1.1.1 进程间的通信方式
进程通信是指进程之间传输信息。
- 管道
- 匿名管道。只能用于具有亲缘关系的父子进程之间或者兄弟进程之间的通信。
- 有名管道。以磁盘文件的方式存在,可以实现本机任意两个进程之间的通信。
- 消息队列。存在拷贝开销的问题
- 共享内存。存在多进程竞争内存的问题,需要依靠某种同步操作,如互斥锁和信号量等。
- 信号量。PV操作维护一个计数器,主要用于解决与同步相关的问题并避免竞争条件。
- 信号
- Socket套接字。主要用于在客户端和服务器之间通过网络进行通信。
1.1.2 进程同步方式
进程同步是控制多个进程按照一定的顺序执行。
- 临界区
- 同步与互斥
- 信号量
- 管程
1.1.3 进程的调度算法
==进程调度,是为了实现最大CPU利用率。==不同环境下的调度算法目标不同。
-
批处理系统
批处理系统没有太多的用户操作,调度算法应保证吞吐量和周转时间(从提交到终止的时间)。
-
先到先服务(FCFS,First-come-first-served)调度算法
FCFS是非抢占式的调度算法,按照请求的顺序进行调度。
FCFS有利于长作业,不利于短作业。因为短作业必须一直等待前面的长作业执行完毕后才能执行,而长作业的执行又需要很长时间,造成了短作业等待时间过长。
-
短作业优先(SJF,shorted job first)调度算法
SJF也是非抢占式的调度算法,按照估计运行时间最短的顺序进行调度。
长作业有可能会饿死,处于一直等待短作业执行完毕的状态。因为如果一直有短作业的到来,长作业则一直无法得到调度。
-
最短剩余时间优先(SRTN,shortest remaining time next)
SRTN是SJF是抢占式版本,按照剩余时间运行时间的顺序进行调度。当一个新的作业到达时,其整个运行时间与当前进程的剩余时间作比较。如果新的进程需要的时间更少,则挂起当前进程,运行新的进程。否则,新的进程等待。
-
-
交互式系统
交互式系统有大量的用户交互操作,调度算法的目标是快速地进行响应。
-
时间片轮转(RR,Round robin)调度算法
将所有就绪态进程按照FCFS先到先服务原则排成一个队列,每次调度时,把CPU时间片分配给队首进程,该进程可以执行一个时间片。当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把CPU时间片分配给队首的进程。
时间片轮转的效率和时间片的大小有关。因为进程切换要保存当前进程的信息,并且载入新进程的信息。如果时间片太小,会导致进程切换的很频繁,在进程切换上会花过多时间。而时间片过长,可能实时性无法保证。
-
优先级调度算法
为每个进程分配优先级,按优先级顺序从高到低进行调度。具有相同优先级的进程以FCFS先到先服务的方式进行调度。
优先级的确定可以依据时间要求、内存要求或任何其他资源要求。
同时,为了防止低优先级的进程永远得不到调度,可以随着时间的推移增加等待进程的优先级。
-
多级反馈队列调度算法
一个进程需要执行 100 个时间片,如果采用时间片轮转调度算法,那么需要交换 100 次。
多级队列是为这种需要连续执行多个时间片的进程考虑,它设置了多个队列,每个队列时间片大小都不同,例如 1,2,4,8,…。进程在第一个队列没执行完,就会被移到下一个队列。这种方式下,之前的进程只需要交换 7 次。
每个队列优先权也不同,最上面的优先权最高。因此只有上一个队列没有进程在排队,才能调度当前队列上的进程。
可以将多级反馈队列调度算法看成是时间片轮转调度算法+优先级调度算法的结合,它既能使高优先级的进程得到响应,又能使短进程迅速完成。是一种被公认认的较好的进程调度算法。UNIX操作系统采取的就是这种。
-
1.1.4 优先级反转
优先级反转是指在使用信号量进行多线程同步时,有可能会出现的一种不合理的现象,即:
高优先级的任务被低优先级任务阻塞(一般是因为共享资源被低优先级任务占用,高优先级任务获取不到),导致高优先级任务迟迟得不到调度。但处于中间优先级的任务却能获得CPU调度。从表面上看,好像是中优先级的任务比高优先级的任务具有更高的优先权。
例如:假定一个进程中有三个线程Thread1(高)、Thread2(中)和Thread3(低),考虑下图的执行情况:
- T0时刻,Thread3运行,并获得同步资源SYNCH1;
- T1时刻,Thread2开始运行,由于优先级高于Thread3,Thread3被抢占(未释放同步资源SYNCH1),Thread2被调度执行;
- T2时刻,Thread1抢占Thread2;
- T3时刻,Thread1需要同步资源SYNCH1,但SYNCH1被更低优先级的Thread3所拥有,Thread1被挂起等待该资源
- 而此时线程Thread2和Thread3都处于可运行状态,Thread2的优先级大于Thread3的优先级,Thread2被调度执行。最终的结果是高优先级的Thread1迟迟无法得到调度,而中优先级的Thread2却能抢到CPU资源。
上述现象中,优先级最高的Thread1要得到调度,不仅需要等Thread3释放同步资源(这个很正常),而且还需要等待另外一个毫不相关的中优先级线程Thread2执行完成(这个就不合理了),会导致调度的实时性就很差了。
解决方法:
-
优先级继承
-
让低优先级线程在获得同步资源的时候(如果有高优先级的线程也需要使用该同步资源时),临时提升其优先级,以使其能更快的执行并释放同步资源。释放同步资源后再恢复其原来的优先级。
与上图相比,到了T3时刻,Thread1需要Thread3占用的同步资源SYNCH1,操作系统检测到这种情况后,就把Thread3的优先级提高到Thread1的优先级。此时处于可运行状态的线程Thread2和Thread3中,Thread3的优先级大于Thread2的优先级,Thread3被调度执行。
Thread3执行到T4时刻,释放了同步资源SYNCH1,操作系统恢复了Thread3的优先级,Thread1获得了同步资源SYNCH1,重新进入可执行队列。处于可运行状态的线程Thread1和Thread2中,Thread1的优先级大于Thread2的优先级,所以Thread1被调度执行。
-
-
优先级天花板
- 当线程申请某共享资源时,把该线程的优先级提升到可访问这个资源的所有线程中的最高优先级,这个优先级称为该资源的优先级天花板。
- 这种方法简单易行,不必进行复杂的判断,不管线程是否阻塞了高优先级线程的运行,只要线程访问共享资源都会提升线程的优先级。
1.1.5 进程状态
进程有五种状态:
- 新建(created)
- 就绪(ready)。进程处于就绪状态时,等待被调度
- 运行(running)
- 阻塞(waiting)。进程处于阻塞状态时,等待资源
- 终止(terminated)
Note:
- 只有就绪态和运行态可以相互转换,其它的都是单向转换。
- 就绪态的进程通过进程调度算法获得CPU时间片,转为运行态。运行态的进程,在分配给它的CPU时间片用完之后就会转为就绪态,等待下一次调度。
- 阻塞状态是缺少需要的资源从而由运行态转换而来,但是这里的资源不包括CPU时间片,缺少CPU时间片会从运行态转为就绪态。
1.1.6 PCB进程控制块
操作系统管理和控制进程,都需要借助PCB来完成。进程和进程控制块的数量始终是相等的,创建多少个进程就会相应产生多少个PCB。当进程执行结束后,操作系统只需要释放相应进程控制块占用的内存空间,目标进程也随之消亡。
操作系统创建每个进程时,都会额外申请一块内存空间,这块内存空间称为PCB,用来存储、管理和控制该进程所需要的信息,例如:
- 进程名称或者ID号,作为该进程的标识
- 当前进程的执行状态,进程在整个执行过程中可能处于新建、就绪、运行、阻塞、终止这5种状态
- 进程占用的各种资源,例如内存大小、使用的IO设备等
- 进程已经执行的时间,占用CPU的时间等
1.1.7 进程的创建和撤销过程
进程允许创建和控制另一个进程,前者称为父进程,后者称为子进程,子进程又可以创建孙进程,如此下去进而形成一个进程的家族树,这样子进程就可以从父进程那里继承所有的资源,当子进程撤销时,便将从父进程处获得的所有资源归还。
此外,撤销父进程,则必须撤销所有的子进程。(撤销的过程实际上就是对这棵家族树进行后序遍历的过程)
在应用中创建一个子进程的过程如下:
- 申请空白的PCB
- 初始化进程描述信息
- 为进程分配资源以及地址空间
- 将其插入就绪队列中
当进程完成后,系统会回收占用的资源,撤销进程。
而引发进程撤销的情况有:进程正常结束或者异常结束,外界的干预(比如我们在任务管理器中强制停止某个进程的运行)。撤销进程的过程如下:
- 查找需要撤销的进程的PCB
- 如果进程处于运行状态,终止进程并进行调度
- 终止子孙进程 - 归还资源
- 将它从所在的队列中移除
1.1.8 为什么要有进程
操作系统之所以要支持多进程,是为了提高CPU
的利用率。
为了切换进程,需要进程支持挂起
与恢复
,不同进程间需要的资源不同,所以这也是为什么进程间资源需要隔离,这也是进程是资源分配的最小单位的原因
切换,细分为两种状态:
- 挂起:保存程序的当前状态,暂停当前程序
- 激活(恢复):恢复程序状态,继续执行程序
1.2 线程
线程,由 线程ID
、程序计数器
、寄存器组合
和堆栈
共同组成,是CPU执行和调度的基本单位。
线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能。
1.2.1 为什么要有线程
进程存在以下问题:
- 单个进程只能干一件事,进程中的代码依旧是串行执行
- 执行过程如果阻塞,整个进程都会挂起
- 多个进程间的内存无法共享,进程间通信比较麻烦
线程的出现就是为了:
- 降低上下文切换的资源消耗
- 提高系统的并发性
- 突破一个进程只能干一件事的缺陷
1.2.2 线程间的同步方式
线程同步,是指多个共享关键资源的线程的并发执行,一般主要有三种同步方式:
-
互斥量
采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。比如Java中的synchronized关键字以及Lock锁都是这种机制。
-
信号量
允许同一时刻多个线程访问同一资源,但是需要控制同时访问资源的最大线程数量
-
事件
通过通知的方式来保持多线程的同步,如wait和notify
1.3 协程
1.3.1 什么是协程
协程,是一种非抢占式(协作式)的任务调度模式,程序可以主动挂起或者恢复执行。协程可以理解为由程序员自己写程序来管理的轻量级线程,对内核不可见。
1.3.2 为什么引入协程
引入进程与线程是为了异步并发地执行任务,提高系统效率及资源利用率。
引入协程的原因:简化异步并发任务。
协程出现的目的:当出现长时间的I/O操作时,通过让出目前的协程调度,执行下一个任务的方式,来消除ContextSwitch上的开销。
1.3.3 协程的特点
-
线程的切换由操作系统负责调度,而协程的切换由用户自己进行调度,因此减少了上下文切换,提高了效率
-
线程的默认Stack大小为1M,而协程更轻量,为1K,因此可以在相同的内存中开启更多的协程
-
避免竞争关系而导致的使用锁
由于在同一线程上,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
-
协程的使用
协程适用于被阻塞的(如I/O操作)、且需要大量并发的场景,当IO操作结束后再自动回调,那么就会大大节省资源并提高性能,从而实现异步编程。但不适用于大量计算的多线程,利用协程来回切换执行计算型的操作,没有任何意义,来回切换并保存状态,反倒会降低性能。
1.3.4 协程与线程的区别
协程是基于线程的,相比于线程轻量很多,可以理解为用户层模拟线程操作。
每创建一个协程,都有一个内核态线程动态绑定,用户态下实现调度、切换,真正执行任务的还是内核线程。
线程的上下文切换都需要内核参与,而协程的上下文切换,完全由用户去控制,避免了大量的中断参与,减少了线程上下文切换与调度消耗的资源。
简单来说,线程是操作系统层面的概念,协程是语言层面的概念。
线程与协程最大的区别在于:线程是被动挂起恢复,协程是主动挂起恢复。
1.4 进程和线程的区别
-
拥有资源
进程是资源分配的基本单位,线程不拥有资源,线程可以访问所在进程中的资源。
-
CPU调度
线程是CPU执行和调度的基本单位。同一进程中,线程的切换不会引起进程的切换,从一个进程中的线程切换到另一个进程中的线程时,会引起进程的切换。
-
系统开销
在创建或撤销进程时,系统都要为其分配或回收资源,如内存空间、IO设备等,需要的系统开销远远大于创建或撤销线程时的开销。
-
通信
线程间可以通过直接读写同一进程的数据进行通信,但是进程通信需要借助一些复杂的方法。