信号量:进程在某一特殊点上被迫停止执行,知道接受到一个对应的变量值,这种特殊变量就是信号量。
void P(semaphore &s){
s.value--;
if (s.value<0) block(s.list); //阻塞本进程并进入s信号量队列
}
void V(semaphore &s){
s.value++;
if (s.value<=0) wakeup(s.list); //唤醒s中的一个进程进入就绪队列
}
s代表可用资源量,P操作请求一个资源,请求过后如果s<0,则代表请求前无资源,则阻塞。V操作释放一个资源,释放后s<=0,则代表阻塞队列仍有进程,则释放进程进入就绪队列。
(1)若信号量为正,该证书表示可对信号量进行的P操作次数,即可用的资源数。信号量初值一般为相关资源总数。
(2)若信号量值为负,绝对值表示在阻塞队列等待资源的进程个数。
进程同步经典问题:
(1)生产者-消费者问题
item B[k]; //缓冲区,长度k
s empty=k,full=0,mutex=1;//可用缓冲区数,可用产品数,互斥信号量
int in=0;
int out=0;
process producer_i(){
while(true){
produce();
P(empty);
P(mutex);
append to B[in];
in=(in+1)%k;
V(mutex);
V(full);
}
}
process consumer_j(){
while(true){
P(full);
P(mutex);
take()from B[out];
out=(out+1)%k;
V(mutex);
V(empty);
consume();
(2)读者-写者问题
int readcount=0;//读进程计数器
seamaphore ws=1,mutex=1;
process reader_i(){
P(mutex);//用以更改对readcount修改权的互斥
readcount++;
if (readcount==1)P(ws);//第一次请求读资源,申请ws资源,使写操作阻塞
V(mutex);
读文件;
P(mutex);//用以更改对readcount修改权的互斥
readcount--;
if(readcount==0)V(ws);
V(mutex);
}
process writer_j(){
P(ws);
写文件;
V(ws);
}
由于写操作排他,读操作允许多个用户同时进行,则写进程可能进入饥饿状态。
写者优先算法:
int readcount=0,writecount=0;
semaphore mrc=1,mwc=1,wr=1,wsem=1,rsem=1;
void reader_i(){
p(wr); //只让最多一个读进程在rsem队列排队
P(rsem); //读者互斥
P(mrc); //对readcount的修改互斥
readcount++;
if(readcount==1) P(wsem); //读写互斥,请求写信号量资源,若失败,等待
V(mrc);
V(rsem);
V(wr);
READ;
P(mrc); //对readcount的修改互斥
readcount--;
if(readcount==0)V(wsem);
V(mrc);
}
void writer_j(){
P(mwc); //对writecount修改互斥
writercount++;
if(writecount==1) P(rsem); //读写互斥,请求读信号量资源,若失败,等待
V(mwc);
P(wsem); //写者互斥
WRITE;
V(wsem);
P(mwc); //对writecount修改互斥
writercount--;
if(writecount==0)V(rsem);
V(mwc);
}
即,在wr队列中排着很多读进程,最前的进入rsem队列里,但rsem里只有一个读进程,但是可以有很多写进程,故实现了写者优先。
3 .哲学家就餐问题
最简单解法:
void philmac(int i){
思考;
去chopsticks[i];
去chopsticks[(i+1)%5];
吃面;
放chopsticks[i];
放chopsticks[(i+1)%5];
}
这种解法可能出现死锁:各个哲学家都先拿起左边的筷子。
编号法,奇数号先拿左边的筷子,偶数先拿右边的筷子
seamphore chopsticks[5];
for(int i=0;i<5;i++)chopsticks[i]=1;
process philmac_i (){
thick();
if(i%2==0){
P(chopstick[i]);
P(chopstick[(i+1)%5]);
}
else {
P(chopstick[(i+1)%5]);
P(chopstick[i]);
}
eat();
V(chopstick[i]);
V(chopstick[(i+1)%5);
}
发放令牌法:比第一种方法多了一个令牌token,最多允许4个哲学家一起吃面,避免死锁
semaphore chopsticks[5]={1,1,1,1,1};
semaphore token=4;
int i;
process philmac_i(){
think();
P(token);
P(chopsticks[i]);
P(chopsticks[(i+1)%5);
eat();
V(chopsticks[(i+1)%5);
V(chopsticks[i]);
V(token);
4.睡眠理发师问题
int waiting=0;//等待理发的顾客数
semaphore customers=0,barbers=0,mutex=1;
process barber(){
while(true){
P(customers); //请求顾客资源,若无,理发师睡眠
P(mutex); //椅子互斥,要么理发师睡,要么顾客坐
waiting--; //等候顾客数-1
V(mutex);
cut_hair();
V(barbers); //释放理发师资源
}
process customer_i(){
P(mutex); //椅子互斥
if(waiting<N){
waiting++; //等候顾客数+1
V(mutex);
V(customers); //唤醒理发师
P(barbers); //请求理发师资源,忙则等待
get_haircut();
}
else V(mutex);
}