“读者--写者”问题
1 必备学习
(一)信号量机制(也称计数信号量/一般信号量)
1 定义
用于进程间传递信号的一个整数值。在信号量上只有三种操作:初始化、递减和增加,且这三种操作都是原子操作。
2 主要操作:
(1)递减操作:可以用于阻塞一个进程。常用semWait原语或者P原语表示,也称wait操作。
关于semWait原语的规范定义如下:
struct semaphore{
int count;
queueType queue;
};
void semWait(semaphore s)
{
s.count--;
if(s.count < 0){
//把当前进程插入到队列中
//阻塞当前进程
}
}
(2)增加操作:可以用于解除阻塞一个进程。常用semSignal原语或者V原语表示,也称signal操作。
关于semSignal原语的规范定义如下:
struct semaphore{
int count;
queueType queue;
};
void semSignal(semaphore s)
{
s.count++;
if(s.count <= 0){
//把当前进程从队列中移出
//取消阻塞当前进程,插入到就绪队列中
}
}
3 操作解释
开始时,信号量的值为0或者正数。如果该值为正数,则该值等于发出semWait操作后可以立即继续执行的进程的数量。如果该值为0,则发出semWait操作的下一个进程会被阻塞(即只有一个进程可以立即执行),之后该信号量的值会变成负数。该负数的值表示了正在阻塞的进程的数量。每一个semSignal操作都会使得信号量加一(也就是解除阻塞一个进程)。
(二)互斥
1 定义
当一个进程在临界区访问共享资源时,其他进程不能进入该临界区访问任何共享资源,这种情形叫做互斥。
临界区:是一段代码,在这段代码中进程将访问共享资源,当另一个进程已经进入到该段代码中运行时,则这个进程就不能在这段代码中执行。
2 信号量解决互斥问题的方法
假设有n个进程想要访问共享资源。每个进程进入临界区之前执行semWait(s),如果s的值为负,则进程被挂起;如果值为1,则减为0,该进程进入临界区;之后由于s的值不再为正,所以其他进程不能进入临界区。
一般将信号量初始化为1,第一个执行semWait操作的进程可以立即进入临界区,并将s置为0。之后的其他进程若试图进入临界区,他们都会发现第一个进程在忙,因此被阻塞。s的值逐次递减(每一个试图进入的进程访问都会使s减1)。当第一个进程离开临界区时,s增加1,一个进程被唤醒,置于就绪队列,下一次操作系统调度时,即可进入临界区。
使用信号量解决互斥问题的方法具体如下所示:
const int n = 10; //假设有10个进程想要进入临界区
semaphore s = 1; //定义一个信号量
void p(int i)
{
while(true){
semWait(s);
//临界区
semSignal(s);
//其他部分
}
}
void main()
{
parbegin(P(1),P(2),...P(10));
//挂起主程序,初始化并行过程P1,P2,P3,...,P10,当P1,P2,P3,...,P10过程全部终止之后,才恢复主程序执行。
}
(三)同步
1 定义
保证访问的时序可控性,使调用资源的顺序合理。它主要源于进程合作,是进程间共同完成一行任务时直接发生相互作用的关系。是进程之间的直接制约关系。
2 同步模式原理
例如:s = 0;有两个进程(P1,P2)想要访问临界区。
首先,若P1访问先执行semWait(s)操作,使得s = -1;P1进入阻塞队列。
然后,进程P2执行semSignal(s)操作,s=0; P1唤醒,转为就绪态。
再次调度时,P1执行semWait(s)操作,和上述过程相同。
所以,当进程P1执行semWait(s)操作之前,除非进程P2已经已经执行一次semSignal(s)操作,才可以保证进程P1可以执行,否则会一直持续上述的过程。也就是说,进程P1受到进程P2的制约,这就要求进程P1必须与进程P2进行同步。
3 信号量实现同步原理
进程获取临界资源之前,要先获取信号量资源;若无信号量资源,则该进程阻塞等待,进入等待队列。若有信号量资源,则对信号量进行P操作,再获取临界资源。当临界资源+1时,对应的信号量资源则执行V操作,然后唤醒在等待队列中等待获取临界资源的进程。
2 读者-写者问题
(一) 问题描述
有一个多个进程共享的数据区,有一些进程(reader)只读取这个数据区中的数据,一些进程(writer)只往数据区中写数据,此外还需满足以下条件:(1)任意多个读进程可以同时读这个文件;(2)一次只有一个写进程可以写文件;(3)如果一个进程正在写文件,禁止任何读进程读取文件。
也就是说:读进程不需要排斥读进程,而写进程需要排斥所有进程(包括读进程和写进程)。
(二)解决方案
1 读者优先
定义一个互斥信号量x,初值为1,代表一个共享文件,解决“读-写”互斥和“写-写”互斥。信号量x用于保证readcount被正确的更新。
定义一个信号量wsem,表示是否允许写,初始值为1。
设置全局变量readcount,用于记录读进程的数目。
具体实现如下:
int readcount; //定义全局变量,用于记录读进程的数目
semaphore x = 1,wsem = 1; //定义用于实现对readcount操作的互斥信号量,定义wsem表示是否可以写的信号量
void reader()
{
while(true){
semWait(x); //开始对readcount进行互斥访问
readcount++; //有了一个读者,读进程数目readcount加一。
if(readcount == 1)
semWait(wsem); //第一个读者进行读,判断是否有写进程在临界区,如果没有,阻塞写进程,否则等待。
semSignal(x); //对readcount结束互斥访问
read();//进行读文件
semWait(x); //对readcount进行互斥访问
readcount--; //读完之后,读进程数目减一
if(readcount == 0)
semSignal(wsem); //最后一个离开临界区的读进程需要判断是否有写进程需要进入临界区,如果有,唤醒一个写进程。
semSignal(x); //对readcount结束互斥访问
}
}
void writer()
{
while(true){
semWait(wsem); //如果没有读进程,进入写进程,如果有,则进行等待。
writer(); //进行写文件
semSignal(wsem); //写进程完成,判断是否有读进程需要进入临界区,若有,唤醒一个读进程进入临界区。
}
}
void main()
{
readcount = 0;
parbegin(reader,writer);
}
2 写者优先
一个写进程想要写数据时,不允许新的读进程访问该数据区。
当没有写者进程时,读者进程可以同时读取文件。
具体实现如下:
在实现读者优先的基础上,需要再设置以下信号量和变量:
信号量rsem:当至少有一个写进程准备访问数据区时,用于禁止所有的读进程。
变量writecount:控制rsem的设置。
信号量y:控制writecount的更新。
信号量z:只允许一个进程在rsem上排队,其他所有读进程在等待rsem之前,在信号量z上进行排队。
int readcount,writecount;
semaphore x = 1,y = 1,z = 1,wsem = 1,rsem = 1;
void reader()
{
while(true)
{
semWait(z); //z减为0,在信号量z上进行排队
semWait(rsem); //rsem减为0,检查是否有写进程,若有写进程,则不允许读进程进入。若没有,一个读进程准备就绪,在操作系统调度时可进入临界区
semWait(x); //开始对readcount进行互斥访问,保证下列操作的原子性
readcount++; //一个读进程,读进程数目加一
if(readcount == 1)
semWait(wsem); //若第一个读者进行读,判断是否有写进程,如果有,读进程等待,如果没有,阻塞写进程。
semSignal(x); //结束对readcount进行互斥访问
semSignal(rsem); //rsem恢复为1,此时若有写进程准备访问临界区,则禁止所有的读进程。
semSignal(z);
read();
semWait(x); //开始对readcount进行互斥访问,保证下列操作的原子性。
readcount--;
if(readcoount == 0)
semSignal(wsem); //最后一个离开临界区的读进程判断是否有写进程,若有,唤醒写进程进入临界区
semSignal(x);
}
}
void writer()
{
while(true){
semWait(y); //开始对writecount进行互斥访问,保证下边操作的原子性
writecount++; //有一个写者,写进程数目加一
if(writecount == 1) semWait(rsem); //如果第一个写进程在写数据,阻塞所有的读进程
semSignal(y); //结束对writecount进行互斥访问
semWait(wsem); //判断是否有写进程,若有,其他进程均不可进入临界区
write();
semSignal(wsem);
semWait(y); //开始对writecount进行互斥访问,保证下边操作的原子性
writecount--;
if(writecount == 0) semSignal(rsem); //最后一个离开临界区的写进程,rsem加一,一个读进程就绪,下次调度即可进入临界区。
semSignal(y);
}
}
void main()
{
readcount = 0;
writecount = 0;
parbegin(reader,writer);
}
以上是自己的一点小见解,如有错误,望您指正!