基本概念
- 进程同步:指多个相关进程在执行次序上的协调。
- 进程互斥:由于各进程要求共享资源,而有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥。
- 临界资源:系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源或共享变量。
- 临界区:在进程中涉及到临界资源的程序段。
在多道程序系统中,系统中可能有多个进程,它们之间存在以下两种关系:
- 相互合作:进程之间存在共享数据而引起的直接相互制约。并发进程之间共享了某些数据、变量等,为了使个进程不致因争夺共享数据而发生混乱,保证数据的完整性,需要正确处理进程协作的问题。
- 资源共享/竞争:由于竞争系统资源而引起的间接相互制约关系。凡是使用共享资源的进程,先向系统提出申请,然后由操作系统的资源管理程序根据资源情况,按一定的策略来实现分配。
使用临界区的原则
- 空闲让进:当临界资源处于空闲状态,允许一个请求进入临界区的进程立即进入临界区,从而有效的利用资源。
- 忙则等待:已经有进程进入临界区时,意味着相应的临界资源正在被访问,所以其他准备进入临界区的进程必须等待,来保证多进程互斥。
- 有限等待:对要求访问临界资源的进程,应该保证该进程能在有效的时间内进入临界区,防止死等状态。
- 让权等待:当进程不能进入临界区,应该立即释放处理机,防止进程忙等待。
进程同步机制
锁
表示某种资源的状态的标志,标志为 0,表示未被使用;标志为 1,表示已被使用。
- 上锁:进程使用某一共享资源之前。检测锁位的值(0 还是 1)。如果原来的值为 0,将锁位置为 1(表示占用资源),如果原来的值为 1(表示资源已被占用),则返回检测。
- 开锁:进程在使用完资源后 将锁位置位 0(表示释放资源)
信号灯
信号灯是一个数据结构,定义如下:
struc semaphore
{
int value;
pointer_PCB queue;
}
通过信号灯的 P、V 操作实现
进程的同步可以通过信号灯的 P、V 操作来实现。P(passeren)通过,理解为申请资源,V(vrijgeven)释放,理解为释放资源。 关键是要分析清楚进程之间的相互关系;还要分析清楚同步进程各自关系的状态。
一般同步问题可以分为两类:
- 保证一组合作进程按逻辑需要所确定的次序执行;
- 保证共享缓冲区/共享数据的合作进程的同步。
P 操作
P(s)
{
s.value = s.value --;
if (s.value < 0)
{
该进程状态置为等待状态将该进程的PCB插入相应的等待队列末尾s.queue
}
}
V 操作
V(s)
{
s.value = s.value ++;
if (s.value < = 0)
{
唤醒相应等待队列s.queue中等待的一个进程改变其状态为就绪态,并将其插入就绪队列
}
}
信号灯集——AND 型信号量集 AND 型信号灯集是指同时需要多种资源。AND 同步机制的基本思想是:将进程在整个运行过程中需要的所有资源,一次性全部地分配给进程,待进程使用完后再一起释放。只要尚有一个资源未能分配给进程,其它所有可能为之分配的资源,也不分配给他。即,对若干个临界资源的分配,采取原子操作方式:要么全部分配到进程,要么一个也不分配。由死锁理论可知,这样就可避免上述死锁情况的发生。为此,在 wait 操作中,增加了一个“AND”条件,故称为 AND 同步。
合作进程的执行次序
前趋图(Precedence Graph):是一个有向无循环图,记为 DAG(Directed Acyclic Graph),前趋图中必须不存在循环,该图通常用于表现事务之间先后顺序的制约关系。
共享缓冲区的合作进程同步
- 生产者-消费者问题:该问题中出现的主要的两种关系:
- 生产者—消费者之间的同步关系:一旦缓冲池中所有缓冲区均装满产品时,生产者必须等待消费者提供空缓冲区;一旦缓冲池中所有缓冲区全为空时,消费者必须等待生产者提供满缓冲区。
- 生产者—消费者之间还有互斥关系:由于缓冲池是临界资源,所以任何进程在对缓冲区进行存取操作时都必须和其他进程互斥进行。
- 哲学家就餐:由 Dijkstra 提出并解决的哲学家进餐问题(The Dinning Philosophers Problem)是典型的同步问题。该问题是描述有五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五只筷子。他们的生活方式是交替地进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。进餐完毕,放下筷子继续思考。
P-V 同步机制的缺点
采用 P-V 同步机制来编写并发程序,对于共享变量及信号灯变量的操作将被分散于各个进程中。
缺点:
- 易读性差,因为要了解对于一组共享变量及信号灯的操作是否正确,则必须通读整个系统或者并发程序;
- 不利于修改和维护,因为程序的局部性很差,所以任一组变量或一段代码的修改都可能影响全局;
- 正确性难以保证,因为操作系统或并发程序通常很大,要保证这样一个复杂的系统没有逻辑错误是很难的。
管程
指关于共享资源的数据及在其上操作的一组过程或共享数据结构及其规定的所有操作,是一种同步机制。系统按资源管理的观点分解成若干模块,用数据表示抽象系统资源,同时分析了共享资源和专用资源在管理上的差别,按不同的管理方式定义模块的类型和结构,使同步操作相对集中,从而增加了模块的相对独立性。
管程的组成
管程的四个组成部分:
- 名称
- 数据结构说明
- 对该数据结构进行操作的一组过程/函数
- 初始化语句
局部于管程的数据结构,仅被局部于管程的过程访问。局部于管程的过程,也仅能访问管程内的数据结构。管程(相当于围墙)把共享变量和对它进行操作的若干过程围起来。
管程的特点
- 模块化
- 抽象数据类型
- 信息掩蔽
管程的要素
- 管程中的共享变量在管程外部是不可见的,外部只能通过调用管程中所说明的外部过程(函数)来间接地访问管程中的共享变量。
- 为了保证管程共享变量的数据完整性,规定管程互斥进入。
- 管程通常是用来管理资源的,因而在管程中应当设有进程等待队列以及相应的等待及唤醒操作
管程模型
多个进程出现在管程中:
- 当一个进入管程的进程执行等待操作时,它应当释放管程的互斥权;
- 当一个进入管程的进程执行唤醒操作时(如P唤醒Q),管程中便存在两个同时处于活动状态的进程。
Hasen 模型:要求唤醒放到最后,这样 P 通知 Q 后,P 就结束了,然后 Q 执行完,这样就能保证同一时刻只有一个线程在执行。
Hoare 模型:P 通知完 Q 后,P 马上阻塞,Q 马上执行;Q 执行完之后再唤醒 P 线程,也能保证同一时刻只有一个线程在执行,但是 P 多了一次阻塞唤醒操作。
MESA 模型:P 唤醒 Q 之后,P 还是会接着执行,Q 并不立即执行,仅仅是从条件变量队列到等待队列中。
管程的实现
两个主要途径:
- 直接构造:效率高
- 间接构造:即用某种已经实现的同步机制去构造。
管程与进程区别
- 目的不同:进程是为了资源分配与调度,管程是为了进程间通信。
- 系统管理数据结构不同:进程是 PCB,管程是等待队列。
- 管程被进程调用。
- 管程是一个编译语言概念,编译器必须要识别管程并用某种方式对其互斥做出安排。
信号灯与管程的缺点
信号灯和管程是较低级、间接的通信方式:
- 信号灯和管程都是用来解决访问公共内存的一个或多个 CPU 上的通信问题的,未提供机器间的信息交换方式。
- 只能传递简单的信号,不能传递交换大量信息。
消息传递
消息是发送进程形成的一个信息块,通过信息的语法表示传送内容到接受进程。明确地将信息从一个进程的地址拷贝到另一个进程的地址空间;进程通过操作系统提供的“发送消息/接受消息”两个原语进行数据交换。 直接通信方式:
- 使用用户空间中的信箱实现;消息会直接挂到接收进程的消息缓冲队列上。
管道
管道是用于连续读写进程的一个共享文件,又名 pipe 文件。进程通信采用管道通信方式时,操作系统会在内存中开辟一个大小固定的缓冲区。
共享存储
相互通信的进程通过共享某些数据结构或存储区来进行通信。进程用这种方式通信时,操作系统只负责提供共享空间和同步互斥工具;两个进程对共享空间的访问必须是互斥的,即同一时间只允许一个进程访问该共享空间。