以下介绍几个几个经典的进程同步的问题。
一、生产者-消费者模型问题
- 模型描述:一组生产者进程和一组消费者进程共享一个初始为空、大小为n的缓冲区,只有缓冲区没满时,生产者才把消息放入缓冲区,否则必须等待;只有缓冲区不空时,消费者才能从中取出消息,否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或一个消费者从中取出消息。
- 分析:
- 进程关系:生产者和消费者对缓冲区互斥访问是互斥关系,同时生产者和消费者是相互协作的,并且只有在生产者生产之后,消费者才能消费,他们也是同步关系。
- 就只有两个进程,存在互斥与同步的关系,需要考虑互斥和同步的PV操作的设置。
- 信号量:信号量mutex作为互斥信号量,用于控制互斥访问缓冲区,互斥信号量初始值为1;信号量full用于记录当前缓冲区装填的生产者生成的资源数量,初始值为0.信号量empty用来记录当前缓冲区剩余大小,初始值为缓冲区总容量n。
- 模型描述如下:
semaphore mutex = 1; //临界区互斥信号量
semaphore empty = n; //空闲缓冲区
semaphore full = 0; //缓冲区初始化为空
Producer(){
while(true){
生产一个资源;
P(empty); //获取空缓冲区单元,empty--
P(mutex); //互斥访问临界区
把资源放入缓冲区;
V(mutex) //释放mutex,退出临界区
V(full) //full++
}
}
Consumer(){
while(true){
P(full); //full--
P(mutex);//互斥访问临界区
从缓冲区取出数据)();
V(mutex); //释放mutex,退出临界区
V(empty); //empty++
把数据拿去使用();
}
}
- 解析:该类问题要注意对缓冲区大小为n的处理,当缓冲区有空时,便可对empty变量进行P操作,一旦取走一个资源便要执行V释放一个缓冲区空间。此外,对empty和full的P操作必须放在对mutex的P操作之前。若生产者先P(mutex),然后执行P(empty);消费者先执行P(mutex),再执行P(full),是不可取的。因为假设缓冲区已经满,生产者继续运行,先执行P(mutex),然后P(empty)时将被阻塞,然后若消费者想从缓冲区取资源,先P(mutex),这时消费者也被阻塞,这样二者无法再继续进行下去了!!!如果消费者进程将缓冲区资源拿完,然后紧接着消费者继续按以上顺序运行,也会造成二者均阻塞的情况。在释放信号量V操作时,先后释放都可以。
二、读者-写者问题
- 有读者和写者两组并发进程,共享一个文件,当两个或以上的读进程同时访问共享数据时没有问题,但是如果某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:①允许多个读者可以同时对文件执行读操作;②只允许一个写者往文件中写信息;③任一写者在完成写操作之前不允许其他读者或写者工作;④写者执行写操作前,应该在满足已有的读者和写者全部退出。
- 问题分析:
- 进程关系的分析:读者和写者是互斥的,写者和写者也是互斥的,而读者和读者不存在互斥。
- 两个进程,即读者和写者。写者与任何进程互斥,用互斥信号量的P操作、V操作即可解决。读者必须实现与写者互斥的同时,实现与其他读者同步,所以用简单的一对P、V操作是无法解决问题的。在这里用到了一个计数器,用它来判断当前是否有读者文件。当有读者时,写者是无法写文件的,此时读者会一直占用文件,当没有读者时,写者才可以写文件。同时,这里不同的读者对计数器的访问也应该是互斥的。
- 信号量设置:首先设置信号量count为计数器,用于记录当前读者的数量,初值为0;设置mutex为互斥信号量,用于保护更新count变量时的互斥;设置互斥信号量rw,用于保证读者和写者的互斥访问。
- 代码如下:
int count = 0; //用于记录当前的读者数量
semaphore mutex = 1; //用于保护更新count变量时的互斥
semaphore rw = 1; //用于保证读者和写者互斥地访问文件
Writer(){
while(true){
P(rw); //互斥访问共享文件
写入; //写入
V(rw); //释放共享文件
}
}
Reader(){
while(true){
P(mutex); //互斥访问count变量
if(count == 0) //当第一个读进程 读共享文件时
P(rw); //阻止写进程写
count++; //读者计数器加一
V(mutex); //释放互斥变量
读取; //
P(mutex);
count--; //读者计数器减一
if(count == 0) //当最后一个读进程读完共享文件
V(rw); //允许写进程写
V(mutex); //释放互斥变量count
}
}
- 在以上算法,读进程是优先的,即当存在读进程时,写操作将被延迟,且只要有一个读进程活跃,随后而来的读进程都将被允许访问文件。这样的方式会导致写进程可能长时间等待,且存在写进程“饿死”的情况。
- 若希望写进程优先,即当有读进程正在读共享文件,有写进程请求访问,这时应该禁止后续读进程的请求,等到已在共享文件的读进程执行完毕,立即让写进程执行,只有在无写进程执行的情况下才允许读进程再次运行。为此,增加一个信号量并在上面程序的Writer()和Reader()中各增加一对PV操作,就可以得到写进程优先的解决程序
int count = 0; //用于记录当前的读者数量
semaphore mutex = 1; //用于保护更新count变量时的互斥
semaphore rw = 1; //用于保证读者和写者互斥地访问文件
semaphore w = 1; //用于实现"写优先"
Writer(){
while(true){
P(w); //在无写进程请求时进入
P(rw); //互斥访问共享文件
写入; //写入
V(rw); //释放共享文件
V(w); //恢复对共享文件的访问
}
}
Reader(){
while(true){
P(w); //在无写进程请求时进入
P(mutex); //互斥访问count变量
if(count == 0) //当第一个读进程 读共享文件时
P(rw); //阻止写进程写
count++; //读者计数器加一
V(mutex); //释放互斥变量
V(w); //恢复对共享文件的访问
读取;
P(mutex);
count--; //读者计数器减一
if(count == 0) //当最后一个读进程读完共享文件
V(rw); //允许写进程写
V(mutex); //释放互斥变量count
}
}
- 这种方法又称为“读写公平法”,即读写进程具有一样的优先级。