同步机制
-
同步机制的原则
1、空闲让进:
当无进程处于临界区时,表明临界资源处于空闲状态,应允许一个请求进入临界区的进程立即进入自己的临界区,以有效利用临界资源
2、忙则等待
当已有进程进入临界区时,表明临界资源正在被占用,因而其他试图进入临界区的进程必须等待,以保证对临界资源的互斥使用
3、有限等待
对要求访问临界资源的进程,应保证在有限时间内能进入到自己的临界区,以免陷入死等的状态
4、让权等待
当进程不能进入自己的临界区时,应立即释放处理机,以避免进程陷入忙等状态 -
硬件同步机制
1、关中断:在进入锁测试之前关闭中断,直到完成锁测试并上锁之后才能打开中断。这样在临界区执行时就不会响应中断,引发调度。
2、Test-and-Set指令实现互斥(原语)
利用硬件指令TS
该指令的一般性描述
boolean TS(boolean *lock){
boolean old;
old=*lock;
*lock=TRUE;//true表示资源正在被占用,false表示空闲
return old;
}
//该指令是原语,不可被分割
进程在进入临界区之前,首先用TS指令测试lock,如果为FALSE则表示可进入,并将TRUE值赋值给lock;
3、利用Swap指令实现进程互斥
该指令称为对换指令,描述如下:
void swap(boolean *a,boolean *b)
{
boolean temp;
temp=*a;
*a=*b;
*b=temp
}
利用Swap指令实现进程互斥但是循环进程可描述如下
do{
key=TRUE;
do{
swap(&lock,&key);
}while(key!=FALSE);
临界区操作;
lock=FALSE;
......
}while(TRUE);
利用上述硬件指令能有效的实现进程的互斥,但当临界资源忙碌时,其他访问进程必须不断地进行测试,处于一种忙等状态,不符合让权等待的原则,同时很难将它们用于解决复杂难度的进程同步问题
- 信号量机制(P、V操作)
信号量(Semaphores)机制(Dijkstra提出)是一种卓有成效的进程同步工具。信号量机制已被广泛应用于单处理机和多处理机系统以及计算机网络中。- 整型信号量
把整型信号量定义为一个用于表示资源数目的整型量S。
它与一般整型量不同,除初始化外,仅能通过两个标准的原子操作(Atomic Operation) wait(S)和signal(S)来访问。一般被称为P、V操作。
- 整型信号量
void wait(S)
{
while(S<=0); //当S<=0时,会不断测试。未处理好
S--;
}
void signal(S)
{
S++;
}
缺陷:若信号量S<=0,就会不断地进行测试。因此,该机制并未遵循让权等待的原则。会使进程处于忙等的状态。
- 记录型信号量
在记录型型号量中,采用了一种新的数据结构,信号S就是这种新的数据结构类型
struct semaphore{
int value; //表示资源数目的整型变量
struct semaphore *list; //进程链表指针
}
相应的,wait(S)和signal(S)操作可描述如下:
wait(semaphore *S)
{
S->value--;
if(S->value<0)
block(S->list);
}
signal(semaphore *S)
{
S->value++;
if(S->value<=0)
wakeup(S->list);
}
在记录型信号量机制中,S->value的初值表示系统中某类资源的数目,因而称为资源信号量,对他的每次wait操作,意味着进程请求一个单位的该类资源,使系统中可供分配的该类资源减少一个。因此描述为S->value–;当S->value<0时,表示该类资源分配完毕,因此进程调用一个block原语进行自我阻塞,放弃处理机并插入到信号量链表S->list中。
对信号量的每次signal操作,都释放一个单位资源,故S->value++表示资源数目加1;若加1后S->value<=0,表示该信号量链表中仍有等待该资源的进程被阻塞,故调用wakeup原语,将S->list 链表中第一个等待进程唤醒。
S.value的绝对值(S.value为负值)表示在该信号量链表中已阻塞进程的数目。
如果S.value的初值为1,表示只允许一个进程访问,此时信号量转化为互斥信号量。
信号量的应用:用信号量实现前趋图。
//设s1s2之间信号量为a,s1s3之间为b,s2s4之间为c,s2s5之间为d,s4s6之间为e
//s5s6之间是f,s3s6之间是g;
//函数p1~p6分别表示为s1~s6的运行函数
p1(){s1;signal(a);signal(b);}
p2(){wait(a);s2;signal(c);signal(d);}
p3(){wait(b);s3;signal(g);}
p4(){wait(c);s4;signal(e);}
p5(){wait(d);s5;signal(f);}
p6(){wait(g);wait(e);wait(f);s6;}
main(){
semaphore a,b,c,d,e,f,g;
a.value=b.value=c.value=0;
d.value=e.value=f.value=0;
g.value=0;
/*将信号量值初始化为0,当s2抢在s1之前运行时,就会被阻塞,进入到阻塞队列。
**只有等到s1执行之后,wait()函数将a.value释放出来,使得a.value=1,
**此时p2才能成功运行。
*/
cobegin
p1();p2();p3();p4();p5();p6();
coend
}
- 经典同步问题
- 生产者-消费者问题
(1)一个生产者,一个消费者,一个buffer
在本题中,生产者在生产时要先判断buffer是否为空,当已有一个产品存在的时候,必须要消费者先拿走一个才能继续生产,否则生产者阻塞,故需要一个信号量empty,来帮助生产者判断buffer的情况;同理,消费者也需要生产者先生产一个产品,才能进行取用,故需要一个信号量full来判断buffer中是否有产品。
semaphore empty,full;
empty.value=1; //初始化为1,表示当前buffer大小为1
full.value=0; //初始化为0,防止消费者在生产者未生产之前运行
cobegin
process Producer(){
produce an item in nextp;
wait(empty);
buffer=nextp; //此时buffer满
signal(full); //表示消费者可以取了
}
process Consumer(){
wait(full);
nextc=buffer; //此步骤将buffer清空
signal(empty); //生产者可以生产
consumer the item in nextc;
}
(2)一个生产者,一个消费者,n个buffer
当为n个buffer时,为了控制存取的位置,需要设置存指针in和取指针out,由于只有一个生产者和消费者,所以in和out不是共享的临界资源。
semaphore empty,full;
int in=0;out=0; //存和取都是从第1个buffer区开始
empty.value=n;
full.value=0;
cobegin
process Producer(){
...
prodece an item in nextp;
wait(empty); //测试只要buffer不满N个,就可以继续存
buffer[in]=nextp;
in=(in+1)%n; //n为buffer的大小
signal(full);
}
process Consumer(){
wait(full);
nextc=buffer[out];
out=(out+1)%n;
signal(empty);
consumer the item in nextc;
}
coend
(3)K个生产者,M个消费者,N个buffer
empty和full只能保证buffer被生产者和消费者有序使用,但是多个消费者之间或者多个生产者之间也会产生冲突,必须设置新的信号量来控制生产者或消费者对buffer的访问,即保证每次只有一个生产者或则消费者进入。
用互斥信号量mutex 实现对缓冲区(共享变量in和out)的互斥使用,互斥信号量mutex初值为1;
semaphore empty,full,mutex;
int in=0,out=0;
empty.value=n;
full.value=0;
item buffer[n];
mutex.value=1; //同时只能有一个消费者或生产者使用in或out指针
void Producer(){
item nextp;
while(true){
...
prodece an item in nextp;
...
wait(empty);
wait(mutex); //对于多个wait的顺序不能颠倒,否则会造成死锁
buffer[in]=nextp;
in=(in+1)%n;
signal(mutex);
signal(full);
}
}
void Consumer(){
item nextc;
while(true){
wait(full);
wait(mutex);
nextc=buffer[out];
out=(out+1)%n;
signal(mutex);
signal(empty);
consumer the item in nextc;
...
}
}
void main(){
cobegin
Producer();
Consumer();
coend
}
在每个进程中的多个wait操作顺序不能颠倒,应先执行对资源信号量(也称私有信号量)的wait操作,然后执行对互斥信号量(公有信号量)的wait操作,否则可能引起进程死锁。(Why?)
设想生产者进程已经将缓冲区放满,消费者进程并没有取产品,即empty = 0。
当下次仍然是生产者进程运行时,它先执行Wait(mutex)封锁信号量(此时mutex=0),再执行Wait(empty)时将被阻塞,希望消费者取出产品后将其唤醒。
轮到消费者进程运行时,它先执行Wait(mutex),然而由于生产者进程已经封锁mutex信号量,消费者进程也会被阻塞,这样一来生产者、消费者进程都将阻塞,都指望对方唤醒自己,陷入了无休止的等待。