管程可以封装在一个模块里面,当某个线程进入该模块的时候,其他线程不允许进入,这实现了一种互斥操作,与此同时由于将函数封装在了该模块中,使得程序的可读性增强,不再像PV原语操作一样复杂难懂,下面介绍管程应用的两个例子:
例1:读者写者问题
问题要求:在内存中的同一块申请读写空间上,允许多个线程访问(读操作)但是只能有一个写操作,为了防止写线程饿死,加入定义如下:在写操作之前有读操作,那么写操作等待其他读操作完毕之后写操作再进行,同时在写操作等待期间的新来读操作也应全部等待,如果读操作之前有写操作,那么等待写操作完毕后再读,同时,在等待期间,新来的写操作不用等待,全部写完之后在允许读。
写操作的主函数如下
start_write();
write();//真实的写操作
finish_write();
在这里应该说明的是:start_write()相当于是线程去抢占资源,只有获得写资格才能执行写操作,finish_write()相当于是线程释放资源,供其他线程去写
那么为了实现上述要求,start_write()应定义成如下形式:
void start_write()
{
write_count++;//写者数量
if(write_count>1||read_count>0)
wait(wq);//wq为条件变量也就是写者紧急队列
}
这里可以发现,读者数量加一,但是这时如果有reader的话,其实并未执行写操作,可以看成只是向读者发送一个写信号,表明写者要开始写了,读者不许再读了。
相应地可以看读者的start
void start_read()
{
if(write_count>0)
wait(rq);
read_count++;
signal(rq);}
从这里可以看到,只要新来的读者发出写信号,即便他没开始写,读者也会进入紧急等待队列等待,这个时候读者也就不能再读了。相当于是给读者发一个锁信号。那么这里为啥是在最后唤醒了rq呢?文章最后给出答案,如果我能记得写的话。
finish_write()定义成如下形式:
void finish_write()
{
write_count--;
if(write_count>0)
signal(wq);//唤醒写者等待队列
else
signal(rq);//唤醒读者等待队列
}
对照的来看读者的finish函数
void finish_read()
{
read_count--;
if(read_count==0)
signal(wq);
}
可以很容易地发现,读者读完之后唤醒不了他自己,而写者可以一直唤醒自己,也就是说在写者等待期间读者不断从管程出去,这样最后可以唤醒写者。
这里可以回答上面抛出的问题,就是signal(rq)为啥放在start_read里面,可以发现,在finish_read里面不能唤醒读者,finish_write只能唤醒一次,也就是说有5个读者也为写操作等待,写只能唤醒一个,所以最好的办法就是一个可以读之后唤醒其他的读者。
小结:这里的管程互斥应该理解成一个线程可以顺利的执行完他自己的函数而没有其他人的干扰。所以不必考虑在某点跳出执行别人的事
对于读者写者问题:
1.在写者那个位置信号量加1,是保证有写者等待而不再接受其他读者
2.signal是保证所有读者一读完就开始写,然后写者都写完之后读者再读。即读者读完唤醒写者,写者写完唤醒写者
或者读者函数改成
void finish_read()
{
read_count--;
if(read_count==0&&write_count!=0)
signal(wq);
else if(write_count==0&&read_count!=0)
signal(rq);
else if(write_count!=0&&read_count!=0
{
//谁也不唤醒
}
}