进程和线程篇
多处理系统的优势
随着处理器的不断增加,我们的计算机系统由单机系统变为了多处理系统,多处理系统的吞吐量比较高,多处理系统拥有多个并行的处理器,这些处理器共享时钟、内存、总线、外围设备等。
多处理系统由于可以共享资源,因此可以开源节流,省钱。整个系统的可靠性也随之提高。
什么是进程和进程表
进程
就是正在执行程序的实例,比如说 Web 程序就是一个进程,shell 也是一个进程,文章编辑器 typora 也是一个进程。
操作系统负责管理所有正在运行的进程,操作系统会为每个进程分配特定的时间来占用 CPU,操作系统还会为每个进程分配特定的资源。
操作系统为了跟踪每个进程的活动状态,维护了一个进程表
。在进程表的内部,列出了每个进程的状态以及每个进程使用的资源等。
什么是线程,线程和进程的区别
这又是一道老生常谈的问题了,从操作系统的角度来回答一下吧。
我们上面说到进程是正在运行的程序的实例,而线程其实就是进程中的单条流向,因为线程具有进程中的某些属性,所以线程又被称为轻量级的进程。浏览器如果是一个进程的话,那么浏览器下面的每个 tab 页可以看作是一个个的线程。
下面是线程和进程持有资源的区别
线程不像进程那样具有很强的独立性,线程之间会共享数据
创建线程的开销要比进程小很多,因为创建线程仅仅需要堆栈指针
和程序计数器
就可以了,而创建进程需要操作系统分配新的地址空间,数据资源等,这个开销比较大。
什么是上下文切换
对于单核单线程 CPU 而言,在某一时刻只能执行一条 CPU 指令。上下文切换 (Context Switch) 是一种 将 CPU 资源从一个进程分配给另一个进程的机制。从用户角度看,计算机能够并行运行多个进程,这恰恰是操作系统通过快速上下文切换造成的结果。在切换的过程中,操作系统需要先存储当前进程的状态 (包括内存空间的指针,当前执行完的指令等等),再读入下一个进程的状态,然后执行此进程。
使用多线程的好处是什么
多线程是程序员不得不知的基本素养之一,所以,下面我们给出一些多线程编程的好处
- 能够提高对用户的响应顺序
- 在流程中的资源共享
- 比较经济适用
- 能够对多线程架构有深入的理解
进程终止的方式
进程的终止
进程在创建之后,它就开始运行并做完成任务。然而,没有什么事儿是永不停歇的,包括进程也一样。进程早晚会发生终止,但是通常是由于以下情况触发的
正常退出(自愿的)
错误退出(自愿的)
严重错误(非自愿的)
被其他进程杀死(非自愿的)
正常退出
多数进程是由于完成了工作而终止。当编译器完成了所给定程序的编译之后,编译器会执行一个系统调用告诉操作系统它完成了工作。这个调用在 UNIX 中是 exit
,在 Windows 中是 ExitProcess
。面向屏幕中的软件也支持自愿终止操作。字处理软件、Internet 浏览器和类似的程序中总有一个供用户点击的图标或菜单项,用来通知进程删除它所打开的任何临时文件,然后终止。
错误退出
进程发生终止的第二个原因是发现严重错误,例如,如果用户执行如下命令
cc foo.c
为了能够编译 foo.c 但是该文件不存在,于是编译器就会发出声明并退出。在给出了错误参数时,面向屏幕的交互式进程通常并不会直接退出,因为这从用户的角度来说并不合理,用户需要知道发生了什么并想要进行重试,所以这时候应用程序通常会弹出一个对话框告知用户发生了系统错误,是需要重试还是退出。
严重错误
进程终止的第三个原因是由进程引起的错误,通常是由于程序中的错误所导致的。例如,执行了一条非法指令,引用不存在的内存,或者除数是 0 等。在有些系统比如 UNIX 中,进程可以通知操作系统,它希望自行处理某种类型的错误,在这类错误中,进程会收到信号(中断),而不是在这类错误出现时直接终止进程。
被其他进程杀死
第四个终止进程的原因是,某个进程执行系统调用告诉操作系统杀死某个进程。在 UNIX 中,这个系统调用是 kill。在 Win32 中对应的函数是 TerminateProcess
(注意不是系统调用)。
进程间的通信方式
进程间的通信方式比较多,首先你需要理解下面这几个概念
- 竞态条件:即两个或多个线程同时对一共享数据进行修改,从而影响程序运行的正确性时,这种就被称为
竞态条件(race condition)
。 - 临界区:不仅
共享资源
会造成竞态条件,事实上共享文件、共享内存也会造成竞态条件、那么该如何避免呢?或许一句话可以概括说明:禁止一个或多个进程在同一时刻对共享资源(包括共享内存、共享文件等)进行读写。换句话说,我们需要一种互斥(mutual exclusion)
条件,这也就是说,如果一个进程在某种方式下使用共享变量和文件的话,除该进程之外的其他进程就禁止做这种事(访问统一资源)。
一个好的解决方案,应该包含下面四种条件
- 任何时候两个进程不能同时处于临界区
- 不应对 CPU 的速度和数量做任何假设
- 位于临界区外的进程不得阻塞其他进程
- 不能使任何进程无限等待进入临界区
- 忙等互斥:当一个进程在对资源进行修改时,其他进程必须进行等待,进程之间要具有互斥性,我们讨论的解决方案其实都是基于忙等互斥提出的。
进程间的通信用专业一点的术语来表示就是 Inter Process Communication,IPC
,它主要有下面 7。种通信方式
消息传递
:消息传递是进程间实现通信和同步等待的机制,使用消息传递,进程间的交流不需要共享变量,直接就可以进行通信;消息传递分为发送方和接收方先进先出队列
:先进先出队列指的是两个不相关联进程间的通信,两个进程之间可以彼此相互进程通信,这是一种全双工通信方式管道
:管道用于两个相关进程之间的通信,这是一种半双工的通信方式,如果需要全双工,需要另外一个管道。直接通信
:在这种进程通信的方式中,进程与进程之间只存在一条链接,进程间要明确通信双方的命名。间接通信
:间接通信是通信双方不会直接建立连接,而是找到一个中介者,这个中介者可能是个对象等等,进程可以在其中放置消息,并且可以从中删除消息,以此达到进程间通信的目的。消息队列
:消息队列是内核中存储消息的链表,它由消息队列标识符进行标识,这种方式能够在不同的进程之间提供全双工的通信连接。共享内存
:共享内存是使用所有进程之间的内存来建立连接,这种类型需要同步进程访问来相互保护。
进程间状态模型
进程的三态模型
当一个进程开始运行时,它可能会经历下面这几种状态
图中会涉及三种状态
运行态
:运行态指的就是进程实际占用 CPU 时间片运行时就绪态
:就绪态指的是可运行,但因为其他进程正在运行而处于就绪状态阻塞态
:阻塞态又被称为睡眠态,它指的是进程不具备运行条件,正在等待被 CPU 调度。
逻辑上来说,运行态和就绪态是很相似的。这两种情况下都表示进程可运行
,但是第二种情况没有获得 CPU 时间分片。第三种状态与前两种状态不同的原因是这个进程不能运行,CPU 空闲时也不能运行。
三种状态会涉及四种状态间的切换,在操作系统发现进程不能继续执行时会发生状态1
的轮转,在某些系统中进程执行系统调用,例如 pause
,来获取一个阻塞的状态。在其他系统中包括 UNIX,当进程从管道或特殊文件(例如终端)中读取没有可用的输入时,该进程会被自动终止。
转换 2 和转换 3 都是由进程调度程序(操作系统的一部分)引起的,进程本身不知道调度程序的存在。转换 2 的出现说明进程调度器认定当前进程已经运行了足够长的时间,是时候让其他进程运行 CPU 时间片了。当所有其他进程都运行过后,这时候该是让第一个进程重新获得 CPU 时间片的时候了,就会发生转换 3。
程序调度指的是,决定哪个进程优先被运行和运行多久,这是很重要的一点。已经设计出许多算法来尝试平衡系统整体效率与各个流程之间的竞争需求。
当进程等待的一个外部事件发生时(如从外部输入一些数据后),则发生转换 4。如果此时没有其他进程在运行,则立刻触发转换 3,该进程便开始运行,否则该进程会处于就绪阶段,等待 CPU 空闲后再轮到它运行。
进程的五态模型
在三态模型的基础上,增加了两个状态,即 新建
和 终止
状态。
- 新建态:进程的新建态就是进程刚创建出来的时候
创建进程需要两个步骤:即为新进程分配所需要的资源和空间,设置进程为就绪态,并等待调度执行。
- 终止态:进程的终止态就是指进程执行完毕,到达结束点,或者因为错误而不得不中止进程。
终止一个进程需要两个步骤:
- 先等待操作系统或相关的进程进行善后处理。
- 然后回收占用的资源并被系统删除。