Bootstrap

操作系统 互斥同步问题

一、进程互斥

由于进程具有独立性和异步性等并发特征,计算机的资源有限,导致了进程之间的资源竞争和共享,也导致了对进程执行过程的制约。

1、临界资源和临界区(临界部分)

临界资源:一次只能供一个进程访问的资源。
临界区:把不允许多个并发进程交叉执行的一段程序称为临界区(critical region)或临界部分(critical section)。

临界区是由属于不同并发进程的程序段共享公用数据或公用数据变量而引起的,临界区不可能用增加硬件的方法来解决。因此,临界区也可以被称为访问公用数据的那段程序。

当一个进程使用该临界资源时,其他需要访问该资源的进程必须阻塞,直到占用者释放该资源。

2、间接制约

把这种由于共享某一公有资源而引起的在临界区内不允许并发进程交叉执行的现象,称为由共享公有资源而造成的对并发进程执行速度的间接制约。这里的“间接”二字主要是指各并发进程的速度受公有资源的制约,而非进程之间的直接制约。

3、互斥

互斥:一组并发进程中的一个或多个程序段,因共享某一公有资源而导致它们必须以一个不允许交叉执行的单位执行。也就是说,不允许两个以上的共享该资源的并发进程同时进入临界区。

一般情况下,作为程序段的一个过程不允许多个进程同时访问它。但如果该过程是纯过程,则各并发进程可以同时访问它。纯过程是指在执行过程中不改变过程自身代码的一类过程。

一组并发进程互斥执行时必须满足如下准则:

平等竞争:不能假设各并发进程的相对执行速度。即各并发进程享有平等地、独立地竞争共有资源的权利,且在不采取任何措施的条件下,在临界区内任意指令结束时,其他并发进程可以进入临界区。
不可独占:并发进程中的某个进程不在临界区时,它不能阻止其他进程进入临界区。
互斥使用:并发进程中的若干个进程申请进入临界区时,只能允许一个进程进入。
有限等待:并发进程中的某个进程从申请进入临界区时开始,应在有限时间内得以进入临界区。
二、互斥的实现

1、互斥的加锁实现

对临界区加锁以实现互斥。当某个进程进入临界区后,它将锁上临界区,直到它退出临界区为止。并发进程在申请进入临界区时,首先测试该临界区是否上锁。

加锁实现中的lock(key[S])和unlock(key[S])均为原子操作。有一点需要注意的是:在系统实现时锁定位key[S]总是设置在公有资源所对应的数据结构中的。

加锁实现的缺点:

循环测试锁定位将损耗较多的CPU计算时间。
另外使用加锁法实现进程间互斥时,还将导致在某些情况下出现不公平的情况。例如进程PA和PB:
进程PA:
A:
lock(key[S])

unlock(key[S])
Goto A
进程PB:

B:
lock(key[S])

unlock(key[S])
Goto B
由于两个进程中均有Goto语句(Goto A和Goto B),这就有可能出现其中一个进程的执行,导致另一个进程长期得不到处理机资源,而处于永久饥饿状态(starvation)。

分析可以知道,一个进程能否进入临界区取决于进程自己调用lock过程去测试相应的锁定位。也就是说,每个进程能否进入临界区是依靠进程自己的测试判断。这样,没有获得执行机会的进程当然无法判断,从而出现不公平现象。

那么是否有办法解决这个问题呢?当然,很明显,办法是有的,我们可以为临界区设置一个管理员,由这个管理员来管理相应临界区的公有资源,它代表可用资源的实体,这个管理员就是信号量。

2、信号量和P、V原语

信号量和P、V原语是荷兰科学家E. W. Dijkstra提出来的。

【信号量】 在操作系统中,信号量sem是一个整数。

sem >= 0时,代表可供并发进程使用的资源实体数;
sem < 0时,表示正在等待使用临界区的进程数。
显然,用于互斥的信号量sem的初值应该大于0,而建立一个信号量必须说明所建信号量代表的意义,赋初值,以及建立相应的数据结构,以便指向那些等待使用该临界区的进程。

【P、V原语】
信号量的数值仅能由P、V原语操作改变。采用P、V原语,可以把类名为S的临界区描述为:When S do P(sem) 临界区 V(sem) od。

一次P原语操作使信号量sem减1
一次V原语操作使信号量sem加1
P原语操作:

sem减1;
若sem减1后仍大于或等于0,则P原语返回,该进程继续执行;
若sem减1后小于0,则该进程被阻塞后进入与该信号相对应的队列中,然后转进程调度。
V原语操作:

sem加1;
若相加结果大于0,V原语停止执行,该进程返回调用处,继续执行;
若相加结果小于或等于0,则从该信号的等待队列中唤醒一个等待进程,然后再返回原进程继续执行或转进程调度。

注意:P、V原语操作(必须以原语实现)都是原子操作,且在P、V原语执行期间不允许中断发生。
这里给出一个使用加锁法的软件实现方法来实现P、V原语:

P(sem):
begin
封锁中断;
lock(lockbit)
val[sem]=val[sem]-1
if val[sem]<0
保护当前进程CPU现场
当前进程状态置为“等待”
将当前进程插入信号sem等待队列
转进程调度
fi
unlock(lockbit);开放中断
end
V(sem):
begin
封锁中断;
lock(lockbit)
val[sem]=val[sem]+1
if val[sem]<=0
local k
从sem等待队列中选取一个等待进程,将其指针置入k中
将k插入就绪队列
进程状态置位“就绪”
fi
unlock(lockbit);开放中断
end
3、用P、V原语实现进程互斥

设信号量sem是用于互斥的信号量,且其初始值为1表示没有并发进程使用该临界区。显然,由前面论述可知,只要把临界区置于P(sem)和V(sem)之间,即可实现进程之间的互斥。

用信号量实现两个并发进程PA和PB互斥的描述如下:
(1)设sem为互斥信号量,其取值范围为(1,0,-1)。其中sem=1表示进程PA和PB都未进入类名为S的临界区,sem=0表示进程PA或PB已进入类名为S的临界区,sem=-1表示进程PA和PB中,一个进程已进入临界区,而另一个进程等待进入该临界区。
(2)实现过程:

Pa:
P(sem)

V(sem)
.
.
.
Pb:
P(sem)

V(sem)
.
.
.
三、进程同步

【进程间的直接制约】:一组在异步环境下的并发进程,各自的执行结果互为对方的执行条件,从而限制各进程的执行速度的过程称为并发进程间的直接制约。这里的异步环境主要是指各并发进程的执行起始时间的随机性和执行速度的独立性。

【进程间的同步】:把异步环境下的一组并发进程因直接制约而互相发送消息而进行互相合作、互相等待,使得各进程按一定的速度执行的过程称为进程间的同步。
具有同步关系的一组并发进程称为合作进程,合作进程间相互发送的信号称为消息或事件。

用消息实现进程同步:


wait(消息名)
表示进程等待合作进程发来的消息。


signal(消息名)
表示向合作进程发送消息。

过程wait的功能是等待到消息名为true的进程继续执行,而signal的功能则是向合作进程发送合作进程所需要的消息名,并将其值置为true。
【进程互斥和进程同步】:进程同步不同于进程互斥,进程互斥时它们的执行顺序可以是任意的。一般来说,也可以把个进程之间发送的消息作为信号量看待。与进程互斥时不同的是,这里的信号量只与制约进程及被制约进程有关,而不是与整租并发进程有关。因此,称该信号量为私用信号量(private semaphore)。一个进程Pi的私用信号量semi是从制约进程发送来的进程Pi的执行条件所需要的信息。与私用信号量相对应,称互斥时使用的信号量为公用信号量。

【用P、V原语实现进程同步】:
首先为各并发进程设置私用信号量,然后为私用信号量赋初值,最后利用P、V原语和私用信号量规定各进程的执行顺序。

四、生产者-消费者问题

把并发进程的同步和互斥问题一般化,可以得到一个抽象的一般模型,即生产者-消费者问题(producer-consumer problems)。
把系统中使用某一类资源的进程称为该资源的消费者,而把释放同类资源的进程称为该资源的生产者。

设生产者进程和消费者进程是互相等效的,其中,各生产者进程使用的过程deposit(data)和各消费者使用的进程remove(data)可描述如下:
首先,可以看到,上述生产者-消费者问题是一个同步问题。即生产者和消费者之间满足如下条件:

(1)消费者想接收数据时,有界缓冲区至少有一个单元是满的。
(2)生产者想发送数据时,有界缓冲区至少有一个单元是空的。
另外,由于有界缓冲区是临界资源,因此,各生产者进程和各消费者进程之间必须互斥执行。

设公用信号量mutex保证生产者进程和消费者进程之间的互斥,设信号量avail为生产者进程的私用信号量,信号量full为消费者进程的私用信号量。信号量avail表示有界缓冲区中的空单元数,初值为n;信号量full表示有界缓冲区中非空单元数,初始值为0。信号量mutex表示可用有界缓冲区的个数,初始值为1。

deposit(data):
begin
P(avail) // 检查是否有空单元可以使用
P(mutex) // 检查临界区是否被占用
送数据入缓冲区某单元
V(full)
V(mutex)
end
remove(data):
begin
P(full) // 检查是否有非空单元
P(mutex) // 检查临界区
取缓冲区中某单元数据
V(avail)
V(mutex)
end
在这个例子中,由于包含多个公用信号量和私用信号量,因此P、V原语的操作顺序需要非常小心。一般来说,由于V原语是释放资源,所以可以以任意次序出现。但P原语则不然,如果次序混乱,将会造成进程之间的死锁。例如:如果我们调换P(full)和P(mutex),便有可能导致程序一直占用临界区,而无法得到资源(如果此时full=0,mutex >= 0,程序进入临界区后得不到资源,但又无法释放临界区),从而导致死锁。

【其他】
设计:先看是否存在互斥,给出公用信号量;再看同步,给出私用信号量。互斥和同步分开操作。
操作:先对同步的私用信号量做P操作,然后对互斥的公用信号量做P操作。

;