《现代操作系统》——第6章 同步互斥机制(二)、进程间通信机制
- 管程 —— 管理共享资源的机制,例如pthread中的Pthread_cond_init()、Pthread_cond_wait()、Pthread_cond_signal()等这样一套同步机制,以及Pthread_mutex_init()...等这样一套互斥机制
- 定义:管程是一个特殊模块,有一个名字,是由关于共享资源的数据结构及在其上操作的一组过程组成。
- 产生背景:由于信号量机制的不足——程序编写困难、易出错,因此在程序设计语言中引入一种高级同步机制,即管程。
- 进程与管程:进程只能通过调用管程中的过程来间接地访问管程中的数据结构。(这点有点像C++类中定义的私有数据只能通过类中的函数来操作)
- 管程要解决的问题:
- 互斥:保证管程中数据结构的数据完整性。管程是互斥进入的,其互斥性由编译器负责保证
- 同步:管程中设置条件变量及等待/唤醒操作以解决同步问题。可以让一个进程或线程在条件变量上等待(此时应先释放管程的使用权),也可以通过发送信号将等待在条件变量上的进程或线程唤醒。
- 管程应用过程中存在的问题及解决方案:
- 问题:是否会出现多个进程同时出现在管程中?
- 场景:
- 当一个进入管程的进程执行等待操作时,他应当释放管程的互斥权
- 当后面进入管程的进程P发现Q等待的条件满足并执行唤醒Q的操作时,管程中便同时存在两个处于活动状态的进程
- 解决方案:
- P等待Q执行——Hoare
- Q等待P继续执行——MESA
- 规定唤醒操作为管程中最后一个可执行的操作——Hansen,并发Pascal
- Hoare管程:
-
Hoare管程说明:
-
因为管程是互斥进入的,所以当一个进程试图进入一个已被占用的管程时,应当在管程的入口处等待。为此,管程的入口处设置一个进程等待队列,称作入口等待队列
- 如果进程P唤醒进程Q,则P等待Q执行;如果进程Q执行中又唤醒了进程R,则Q等待R执行;...,如此,在管程内部可能出现多个等待进程,因此在Hoare管程机制在管程内设置一个进程等待队列,称为紧急等待队列,紧急等待队列的优先级高于入口等待队列的优先级。
-
- Hoare管程——条件变量的实现
- 条件变量:在管程内部说明和使用的一种特殊类型的变量。对于条件变量,可以执行wait和signal操作。
- wait(c):如果紧急等待队列非空,则唤醒第一个等待者;否则释放管程的互斥权,执行此操作的进程进入c链的末尾(这里c是一个条件变量condition,c链指的是条件变量的等待队列)。注意这里先检查紧急等待队列是否有等待的进程,因为紧急等待队列中的进程是signal别的进程而自己主动让位的进程,因此具有优先权,其优先权高于入口等待队列的进程。当然,如果紧急等待队列中没有等待进程,释放管程的互斥权后,入口等待队列中的第一个会获得该管程的互斥权(使用权)。
- signal(c):如果c链为空,则相当于空操作,执行此操作的进程继续执行;否则唤醒c链上第一个等待着,执行此操作的进程进入紧急等待队列的末尾。
-
- 管程的应用:
- 管程的实现方式:
- 直接构造:效率高
- 间接构造:用某种已实现的同步机制构造管程,如用信号量及PV操作
- 管程的实现和使用方式——以生产者消费者问题为例:
- 说明:这里实现管程的思路和信号量及PV操作类似,但具体实现根据不同的机制有所差别(比如按HOARE和MESA机制对wait和signal的内部实现肯定不同)。这里体现了使用管程相比于直接用信号量及PV操作的好处:(1)用户只需要使用insert和remove这样的操作即可,非常方便;(2)不用将复杂的进程同步语句写在应用代码里,逻辑清晰,代码简洁。
- 管程的实现方式:
- MESA管程:
- 产生背景:HOARE管程存在一个缺点——两次额外的进程切换———P唤醒Q,Q执行完再唤醒P。
- 解决:signal->notify。notify:当一个正在管程中的进程执行Notify(x)时,它使得x条件队列得到通知,发信号的进程继续执行
- MESA管程的缺点:
- Notify的结果:位于条件队列头的进程不是立即执行,而是在将来合适的时候且当处理器可用时才能恢复执行
- 由于不能保证在它之前没有其他进程进入管程而改变了条件,因此这个进程必须重新检查条件——用while循环代替if语句对条件进行检查
- 因此导致对条件变量至少多一次额外的检测,并且对等待进程在notify之后何时运行没有任何限制
- MESA管程机制解决生产者消费者问题时的管程实现:
- 说明:采用MESA管程机制的时候,对条件的判断必须用while而不能用if,在POSIX的pthread库中,管程的实现也是采用了MESA机制,后面将会看到
- 对Notify的改进:
- 给每个条件原语关联一个监视计时器,不论是否被通知,一个等待超时的进程将被设为就绪态,防止当某些进程在产生相关条件的信号之前失败时,等待该条件的进程被无限期地推迟而饿死
- 当等待超时而被设为就绪态的进程被调度时,会再次检查相关条件,如果条件满足则执行。
- BroadCast的引入:使所有等待在该条件上的进程都被释放并进入就绪队列。当一个进程不知道有多少进程将被激活时,这种方式非常方便(例如生产者不知道生产出的产品能满足多少个消费者时),当一个进程难以准确判断该激活哪个进程时,也可以使用广播BroadCast。
- HOARE管程与MESA管程的比较:
- MESA管程比HOARE管程出错少
- 在MESA管程中,由于每个进程在收到信号后都重新检查管程变量,并且由于使用了while结构,一个进程不正确的broadcast广播或发信号Notify,不会导致收到信号的程序出错
- pthread中的同步互斥机制:
- pthread解决生产者消费者问题:
- 说明:
- 这里为什么在wait之前上锁?——防止wait操作在完成将线程加入条件变量等待队列之前,切换到另一个进程并加入到该条件的等待队列,这样就会产生顺序错误,破坏了条件变量的等待队列。(APUE上说是为了保护条件变量,因为后面对buffer的修改代码属于临界区)
- 注意这里使用了while循环判断条件,这是因为pthread管程用的是MESA机制
- 关于wait操作:wait的执行分为3步:(1)释放锁;(2)进入条件变量的等待队列;(3)当收到signal或broadcast时获取锁
- 进程间通信——IPC
- 有了信号量和管程,为什么还需要其他通信机制?
- 信号量和管程的不足:二者只能传递简单消息,无法传递大量数据;
- 管程不使用与多处理器情况
- 基本通信机制:
- 消息传递
- 共享内存
- 管道
- 套接字
- 远程过程调用
- 典型操作系统的进程同步/通信机制:
- 原子操作简介:
- 不可分割,在执行完之前不会被其他任务或事件中断
- 常用与实现资源的引用计数
- 屏障:
- 一种同步机制(又称栅栏、关卡)
- 用于对一组线程进行协调
- 应用场景:一组线程协同完成一项任务,需要所有线程到达一个汇合点后再一起向前推进
- 有了信号量和管程,为什么还需要其他通信机制?