全目录:
目录
第四章 进程同步
4.1 进程同步的概念
1. 主要任务:使并发执行的诸进程之间能够有效地共享资源和相互合作,从而使程序的执行具有可再现性
2. 进程间的制约关系:互斥关系,同步关系
3. 临界资源:系统中某些资源一次只允许一个进程适用,称这样的资源为临界资源或互斥资源或共享变量
4. 竞争条件:两个或多个进程并发访问共享数据,共享数据的最终结果取决于进程运行的精确时序,这种情况称为竞争条件,要解决竞争条件的问题,关键是要找出某种途径防止多个进程同时访问共享数据
5. 临界区:把使用临界资源的那一部分程序片段称为程序的临界区,通过某种机制保证一次只能有一个程序在临界区中
do{
entry section //进入区
critical section //临界区
exit section //退出区
reminder section //剩余区
}while(1)
6. 互斥:以某种手段确保当一个进程在使用一个共享变量或文件时,其他进程不能做同样的操作
7. 同步机制应遵循的准则:空闲让进,忙则等待,有限等待,让权等待
8. 进程同步机制:软件同步机制,硬件同步机制,信号量机制,管程机制
4.2 软件同步机制
4.2.1 忙等待形式的互斥
1.关闭中断:
每个进程在进入临界区后先关中断,在离开之前再开中断
缺点:把关中断的权利交给用户进程是不明智的,对于多处理器系统,关中断仅仅对执行本指令的CPU有效,其他CPU仍将继续运行,并可以访问共享内存
2. 锁变量:
对于某一个临界区,在它之外设置一个共享(锁)变量初值为0,当一个进程想进入其临界区时,首先测试这把锁:
如果锁 = 0,则设置为1并进入临界区
如果锁 = 1,则进程一直等待值变成0
3. 严格交替法:
忙等待:进程持续地检测一个变量直到它具有某一特定值,忙等待没有做任何有意义的事,浪费了CPU时间
正常情况:假设两个进程,首先A进程进入、使用临界区后,修改turn的值为1,然后进入非临界区;然后B进程试图进入,判断可以进入,就进入临界区;B执行完后,修改turn的值为0,进入自己的非临界区;A、B依次轮流执行
异常:如果进程A的非临界区很快能执行完,而进程B的非临界区却非常慢,则会:
此时虽然实现了互斥,但浪费了资源,违背了有空让进:进程A被一个临界区以外的进程阻塞
4. 彼得森解决方案:假设所有读(LOAD)和写(STORE)指令都是原子的——不可中断的
//假设两个进程p0和p1共享变量:
int turn; //表示轮到谁进入临界区
Boolean flag[2] //表示一个进程是否准备进入临界区,flag[I]=true表示进程pi准备进入临界区
while (true)
{
flag[i] = TRUE;
turn = j;
while ( flag[j] && turn = = j);
CRITICAL SECTION
flag[i] = FALSE;
REMAINDER SECTION
}
while (true)
{
flag[j] = TRUE;
turn = i;
while ( flag[i] && turn = = i);
CRITICAL SECTION
flag[j] = FALSE;
REMAINDER SECTION
}
Peterson算法满足三个需求;解决了两个进程的临界区问题
4.3 硬件同步机制
4.3.1 忙等待形式的互斥
1. 关闭中断:同上
4.3.2 同步硬件
无论硬件解决方案还是软件解决方案都是基于“锁”的概念来保护临界区,硬件方案解决临界区问题可以使编程更容易,执行速度更快,但是需要特殊的硬件支持,硬件方案使用特殊的硬件指令,该指令是原子的
1. TestAndSet()指令
//定义全局布尔变量lock,初值为FALSE。
//TestAndSet()返回一个传递给它的布尔值,并将全局变量设置为TRUE。
boolean TestAndSet (boolean *target)
{
boolean rv = *target; // rv set to the value of target
*target = TRUE; // target set to TRUE
return rv; // return the value passed to
} TestAndSet() via rv
do {
while (TestAndSet (&lock)) ; // do nothing
//critical section
lock = FALSE;
// remainder section
} while (TRUE);
2. Swap()指令
//定义共享的布尔变量lock,初值为FALSE。
//为每个进程定义一个局部的布尔变量key。
void Swap (boolean *a, boolean *b)
{
boolean temp = *a;
*a = *b;
*b = temp:
}
do {
key = TRUE;
while ( key = = TRUE)
Swap (&lock, &key );
//critical section
lock = FALSE;
// remainder section
} while (TRUE);
TestAndSet()和Swap()都可以提供互斥,但是不一定满足有限等待,除非进行特别的设计
4.4 信号量机制
4.4.1 信号量
两个或多个进程可以通过简单的信号进行合作,一个进程可以被迫在某个位置停止,知道它接受到一个特定的信号
只支持两种特殊的原子操作:wait()和signal(),wait操作又称为P操作或down操作,signal操作又称为V操作或up操作
4.4.2 信号量的使用
1. 计数信号量:信号量的值可以是任何非负数,信号量的值表示可用资源的数量
2. 二进制信号量(互斥信号量): 信号量的值只能是0或1。两个或多个进程使用初值为1的信号量可以保证同时只有一个进程可以进入临界区
4.4.3 信号量的实现
1.忙等:当一个进程处于临界区中,而其它进程试图进入它们的临界区时,必须不停的执行空循环,这个空循环浪费了CPU时间,称为忙等。这种类型的信号量又称为自旋锁
4.4.4 死锁和饥饿
1. 死锁:两个或多个进程无休止地等待某个事件发生,而该事件只能由等待进程中的某个进程产生
2. 饥饿:无限阻塞,进程永远无法从它被挂起的队列中移除
4.5 管程机制
4.5.1 信号量同步的缺点
1. 同步操作分散 2. 易读性差 3. 不利于修改和维护 4. 正确性难以保证
4.5.2 管程机制
1. 定义:管程师关于共享资源的数据结构及一组针对该资源的操作过程所构成的软件模块
2. 特性:模块化,抽象数据类型,信息封装
3. 实现要素:管程中的共享变量在管程外部是不可见的,外部只能通过调用管程中所说明的外部过程(函数)来间接地访问管程中的共享变量,任意时刻管程中只能有一个活跃进程
4. 格式:
1.名称:为每个共享资源设立一个管程
2.数据结构说明:一组局部于管程的控制变量
3.操作原语:同实现要素
4.初始化代码:对控制变量进行初始化的代码
monitor monitor-name{
//share variable declarations
procedure P1(...){
...
}
...
procedure Pn(...){
...
}
Initialization code(...){
...
}
...
}
5. 条件变量:当进入管程的进程因字眼被占用等原因不能继续运行时使其等待,为此在管程内部可以说明和使用一种特殊类型的变量:条件变量
4.5.3 管程和信号量的比较
1. 由于管程的机制,在某个时刻,只能有一个管程过程是活跃的,就类似于原语操作一样,也能够很好地解决竞争条件的问题,而且更为简洁,在使用的过程中,能够直接分析代码,更容易理解使用,而且它的互斥操作是由编译器所支持的,更为安全,不易出错
2. 用信号量可实现进程间的同步,但由于信号量的控制分布在整个程序中,其正确性分析很困难。管程是管理进程间同步的机制,它保证进程互斥地访问共享变量,并方便地阻塞和唤醒进程,管程可以函数库的形式实现,相比之下,管程比信号量好控制
3. 弊端:支持管程的编程语言太少,因为它要求编译器的支持
4.6 经典进程的同步问题
4.7 Linux进程同步机制
(未完待续)