整型信号量定义为一个用于表示资源数目的整型量,除了初始化外,只能由wait(S)和signal(S)这两个操作访问。wait又称为P操作,signal又称为V操作。
wait(S){
while(S<=0); /*do no-op*/
S--;
}
signal(S){
S++;
}
在wait操作中,当S<=0,表示资源数目不够,这时就会一直在while循环中,并什么都不做,使得进程处于“忙等”状态。
wait和signal可以理解为对资源数目的-1和+1,一次wait操作表明要使用一个资源,一次signal操作表明归还一个资源或者产生一个资源。
记录型信号量可以解决上述的“忙等”问题,不会一直停留在wait操作中,但是会出现多个进程等待访问同一临界资源的情况。为此除了表示资源数目的整型变量为value,还增加了一个进程链表指针list,用于链接上述的所有等待进程。
记录型数据结构
typedef struct{
int value;
struct process_control_block *list;
}semaphore;
wait和signal操作
wait(semaphore *S){
S->value--;
if(S->value<0) block(S->list);
}
signal(semaphore *S){
S->value++;
if(S->value<=0) wakeup(S->list);
}
在wait操作中,先自减,如果资源数目小于0,说明资源不够用,这时就要把该进程从运行状态block为阻塞状态 ,进程需要等待,但是该进程还是获取了需要的资源。此时S->value的绝对值表示在该信号量链表中已阻塞进程的数目。
在signal操作中,先自增即释放资源,如果资源数目小于等于0,则需要唤醒链表中的进程,把它从阻塞状态改变为就绪状态。
当S->value=1,表示只允许一个进程访问临界资源此时信号量转变为互斥信号量,用于进程互斥。
前面两个信号量只能解决多个进程对应一种资源的情况,当多个进程需要多种资源,就需要用到AND型信号量。
死锁问题:
假设两个进程A和B,都需要访问互斥型信号量Dmutex=1和Emutex=1。
process A: process B:
step1: wait(Dmutex); wait(Emutex);
step2: wait(Emutex); wait(Dmutex);
当A和B都进行完step1操作时,Dmutex=0,Emutex=0.进行step2操作时,Emutex=-1,Dmutex=-1,则A和B都被阻塞。
这时A和B就进入死锁状态。
AND型同步机制的基本思想:将进程在整个运行过程中需要的所有资源一次性全部分配给进程,待进程使用完后再一起释放。只要有一个资源未能分配给进程,其他所有可能为之分配的资源也不分配给它。
Swait(S1,S2,....Sn){
while(TRUE){
if(S1>=1&&S2>=1&&.....Sn>=1){
for(i=1;i<=n;i++)Si--;
break;
}
else{
由于有资源未能分配给进程,其他所有资源都不分配给它
}
}
}
Ssignal(S1,S2,....Sn){
while(TRUE){
for(i=1;i<=n;i++){
Si++;
}
}
}
在上述操作中,wait和signal都只能对信号量+1和-1,也就一位置不能对临界资源进行多个单位的申请和释放。当需要N个单位的资源时,要进行N个wait操作,很低效,此外当所申请的资源数量低于某一个下限值,不予以分配,从而保证系统的安全性。
设该资源的分配下限值为ti,即最大申请值不能超过ti,对该资源申请量为di。当Si Swait(S1,t1,d1,....Sn,tn,dn) Ssignal(S1,d1,....Sn,dn) 特殊情况: 1.Swait(S,d,d)。此时在信号量集中只有一个信号量S,但允许它每次申请d个资源,当现有资源少于d时,不予分配。 2.Swait(S,1,1)。此时信号量集已经蜕化为一般的记录型信号量(S>1时)或互斥信号量(S=1时)。 3.Swait(S,1,0)。当S>=1时,允许多个进程进入某特定区;当S=0,将阻止任何进程进入特定区。 两个进程的互斥 设互斥信号量mutex=1,初值为1,取值范围为(-1,0,1)。当mutex=1时,表示两个进程皆未进入需要互斥的临界区;当mutex=0时,表示有一个进程进入临界区运行,另外一个必须等待,挂入阻塞队列;当mutex=-1,时,表示有一个进程正在临界区运行,另外一个进程因等待而阻塞在信号量队列中,需要被当前已经在临界区运行的进程退出时唤醒。 mutex=0和mutex=-1时都需要阻塞,但是mutex=-1时阻塞的进程需要被唤醒,即wakeup操作。 在利用信号量机制实现进程互斥时应该注意,wait(mutex)和signal(mutex)必须成对地出现。缺少wait(mutex)将会导致系统混乱,不能保证对临界资源的互斥访问;而缺少signa(mutex)将会使临界资源永远不被释放,从而使因等待该资源而阻塞的进程小能被突。 补充: 一个访问临界资源的循环进程描述如下: 在每个进程中访问临界资源的那段代码称为临界区(riticalsection)。显然,若能保证诸进程互斥地进入自己的临界区,便可实现诸进程对临界资源的互斥访问。为此,每个进程在进入临界区之前,应先对欲访问的临界资源进行检查,看它是否正被访问。如果此刻临界资源未被访问,进程便可进入临界区对该资源进行访问,并设置它正被访问的标志;如果此刻该临界资源正被某进程访问,则本进程不能进 入临界区。因此,必须在临界区前面增加一段用 于进行上述检查的代码,把这段代码称为进入区(enry section)。 相应地,在临界区后面也要加上一段称为退 出区(exit sction)的代码,用于将临界区正被访问的标志恢复为末被访间的标志。进程中除上述进入区、临界区及退出区之外的其它部分的代码在这里都称为剩余区。 看看就懂,不过多解释。 该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。 假定在生产者和消费者之间的公用缓冲池中具有n个缓冲区,这时可利用互斥信号量mutex实现诸进程对缓冲池的互斥使用;利用信号量empty和full分别表示缓冲池中空缓冲区和满缓冲区的数量。又假定这些生产者和消费者相互等效,只要缓冲池未满,生产者便可将消息送入缓冲池;只要缓冲池未空,消费者便可从缓冲池中取走一个消息。对生产者-消费者问题可描述如下: 用于实现互斥的信号量必须成对出现。 对于empty和full的wait和signal操作,也需要成对出现,但它们分别处于不同的进程中。 只是把两个wait变为一个Swait,两个signal变为一个Ssignal 。 有五个哲学家,他们的生活方式是交替地进行思考和进餐,哲学家们共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五支筷子,平时哲学家进行思考,饥饿时便试图取其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐,该哲学家进餐完毕后,放下左右两只筷子又继续思考。 经分析可知,放在桌子上的筷子是临界资源,在一段时间内只允许一位哲学家使用。为了实现对筷子的互斥使用,可以使用一个信号量表示一只筷子,由这五个信号量构成信号量数组。其描述如下: semaphore chopstick[5]={1,1,1,1,1} 所有信号量均被初始化为1,第i位哲学家的活动可以描述为: 假如五位哲学家同时拿起左边的筷子时,就会使五个信号量chopstick 均为0:当他们再试图去拿右边边的筷子时,都将因无筷子可拿而无限期地等待。对于这样的死锁问题,可采取以下几种解决方法: 利用AND信号量机制可以解决上述死锁 3.读者——写者问题 1.允许多个读者可以同时对文件执行读操作 等我有时间补充
信号量的应用
1.利用信号量实现进程互斥
semaphore mutex=1;
PA(){
while(1){
wait(mutex);
临界区;
signal(mutex);
剩余区;
}
}
PB(){
while(1){
wait(mutex);
临界区;
signal(mutex);
剩余区;
}
}
while(TRUE){
进入区
临界区
退出区
剩余区
}
2.利用信号量实现前趋关系
var a1,a2,a3,a4,a5,a6,a7:semaphore: =0;
begin
parbegin
s1:begin s1; V(a1) ; V(a2) ; end
s2:begin P(a1) ; s2 ; V(a4) ; V(a5) ; end
s3:begin P(a1) ; s3 ; V(a6) ; end
s4:begin P(a2) ; s4 ; V(a6) ; end
s5:begin P(a2) ; s5 ; V(a6) ; end
s6:begin P(a3) ; P(a4) ; P(a5) ; s6 ; end
典型例题
1.生产者——消费者问题
1.利用记录型信号量解决生产者消费者问题
int in=0, out=0;
item buffer[n];
semaphore mutex =l, empty=n, full=0;
void proceducer() {
do {
producer an item nextp;
......
wait(empty);//申请一个空的缓冲区
wait(mutex);//申请互斥信号量,不准其他进程进入
buffer[in] =nextp;//产品送入
in=(in+1) % n;//下一个生产者
signal(mutex);//释放互斥信号量.准许其他进程进入
signal(full);//释放一个满的缓冲区
}while(TRUE);
}
void consumer(){
do {
wait(full);//申请一个满的缓冲区
wait(mutex);//申请互斥信号量,不准其他进程进入
nextc= buffer[out];//产品拿出
out =(out+l) % n;//下一个消费者
signal(mutex);//释放互斥信号量.准许其他进程进入
signal(empty);//释放一个空的缓冲区
consumer the item in nextc;
...
}while(TRUE);
}
void main {
cobegin
producer();
consumer();
coend
}
2.利用AND信号量解决生产者——消费者问题
int in= 0, out= 0;
item buffer[n];
semaphore mutex = 1, empty = n, full= 0;
void producer{
do{
producer an item nextp;
Swait(empty, mutex);
buffer[in] = nextp;
in=(in+1) % n;
Ssignal(mutex, full);
}while(TRUE);
void consumer(){
do {
Swait(full, mutex);
nextc = buffer[out];
out= (out+1)% n;
Ssignal(mutex, empty);
consumer the item in nextc;
}while(TRUE);
}
2.哲学家进餐问题
1.利用记录型信号量解决哲学家进餐问题
do{
wait(chopstick[i]);//拿左边筷子
wait(chopstick[(i+1)%5)]);//拿右边筷子
...
//eat
...
signal(chopstick[i]);//放下左边筷子
signal(chopstick[(i+1)%5]);//放下右边筷子
...
//think
...
}while(TRUE)
(1)至多只允许有四位哲学家同时去拿左边的筷子,最终能保证至少有一位哲学家能够进餐,并在用毕时能释放出他用过的两只筷子,从而使更多的哲学家能够进餐。
(2)仅当哲学家的左、右两只筷子均可用时,才允许他拿起筷子进餐。
(3)规定奇数号哲学家先拿他左边的筷子,然后再去拿右边的筷子:而偶数号哲学家则相反。按此规定,将是1、2号哲学家竞争1号筷子: 3、4号哲学家竞争3号筷子。即五位哲学家都先竞争奇数号筷子,获得后,再去竞争偶数号筷子,最后总会有一位哲学家能获得两只筷子而进餐。semaphore chopstick[5]=(1, 1,1,1, 1);
void philosopher(int i)
while(true){
if(¡ mod2 =0){//偶数哲学家,先右后左。
wait (chopstick [(¡ + 1) mod 5]);
wait (chopstick [ï]);
eat();
signal (chopstick [i);
signal (chopstick[(i+ 1) mod 5]);
}
else{//奇数哲学家,先左后右。
wait (chopstick [i]);
wait (chopstick [(i+ 1) mod 5]);
eat;
signal (chopstick [(i+1) mod 5]);
signal (chopstick [i]);
}
}
2.利用AND信号量机制解决哲学家就餐问题
semaphore chopstick[5]= [1,1,1,1,1];
do{
//think
Swait(chopstick[(i+ 1)%5], chopstick[i]);
//eat
Ssignal(chopstick[(i+1)%5], chopstick[i]);
}while[TRUE];
2.只允许一个写者往文件写信息
3.任一写者在完成写操作之前不允许其他读者后写者工作
4.写者执行写操作前,应该让已有的读者或者写者全部退出