读者与写者问题
1.问题描述
有读者和写者两组并发进程,共享一个文件,当两个或以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:
①允许多个读者可以同时对文件执行读操作;
②只允许一个写者往文件中写信息;
③任一写者在完成写操作之前不允许其他读者或写者工作;
④写者执行写操作前,应让已有的读者和写者全部退出。
2.问题分析
读者和写者是互斥的,写者和写者也是互斥的,而读者和读者不存在互斥问题。
两个进程,即读者和写者。写者是比较简单的,它和任何进程互斥,用互斥信号量的P操作、V操作即可解决。读者的问题比较复杂,它必须实现与写者互斥的同时还要实现与其他读者的同步,因此,仅仅简单的一对P操作、V操作是无法解决的。那么,在这里用到了一个计数器,用它来判断当前是否有读者读文件。当有读者的时候写者是无法写文件的,此时读者会一直占用文件,当没有读者的时候写者才可以写文件。同时这里不同读者对计数器的访问也应该是互斥的。
3.读者优先
读者优先就是在读者阅读的时候不会受到写者的干扰,可以正常的进出使用资源,在资源被占用的情况下,写者只能够等待读者先阅读完资源后使用,因此,在读者进程中,使用一个countblock将计数器锁住,使读者之间访问计数器时互斥,用srcblock将书籍资源锁住,使读者与写者之间互斥,于是就用了两个信号量,看似挺简单的但相比于生产者消费者问题还是复杂了一些,信号量的PV操作在这里需要在多个线程使用并且交错使用,用代码实现大概流程是这样的:
1.在读者进程第一部分,先用countblock锁住计数器,读者计数器开始计数,这时候要进入一个读者,如果此时没有人在使用资源,那么srcblock锁住资源保证只有读者能阅读,然后释放countblock,以便给下一个读者使用。
2.在读者进程第二部分就是读者阅读的过程了。
3.在读者进程第三部分,读者阅读完以后,再次需要用到计数器,那么步骤一样,先用countblock将计数器锁住,然后计数器开始自减1,让阅读完的读者离开,再来个判断,如果此时没人阅读,那么将释放srcblock锁,让书籍资源处在空闲状态,执行完以后释放countblock计数器的锁~以便给下一个读者使用。
4.在写者进程,只需要在开头和结尾分别使用PV操作就可以了,中间部分就是写者做的事。
源代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define P sem_wait
#define V sem_post
#define countblock &countBlock
#define srcblock &fileSrc
sem_t countBlock;
sem_t fileSrc;
sem_t oneSignal;
int readercount=0;
int sum=30;
void* Reader(void *p)
{
while(sum)
{
sum--;
P(countblock); //申请计数器资源
if(readercount==0){
P(srcblock); //申请书籍资源
}
readercount++;
V(countblock); //释放计数器资源
/** 读者阅读区域 **/
printf("%d位读者正在阅读..
",readercount);
/** 读者阅读区域 **/
P(countblock); //申请计数器资源
readercount--;
if(readercount==0){
V(srcblock); //释放书籍资源
}
V(countblock); //释放计数器资源
}
}
void* Writer(void *p)
{
while(sum)
{
sum--;
P(srcblock); //申请书籍资源
printf("写者正在写作ing...
");
V(srcblock); //释放书籍资源
}
}
int main()
{
int i;
/** 初始化资源个数 **/
sem_init(equalblock, 0, 1);
sem_init(oneblock, 0, 1);
sem_init(countblock,0,1);
sem_init(srcblock,0,1);
/** 创建一个写者进程 **/
pthread_t tidw;
pthread_create(&tidw, NULL, Writer, NULL);
/** 创建多个读者进程 **/
for(i=0;i<10;i++){
pthread_t i;
pthread_create(&i, NULL,Reader, NULL);
}
/**观察结果后关闭进程**/
getchar();
pthread_exit(0);
return 0;
}
运行结果:
4.写者优先
如果是希望写者优先,那么当写者需要写作时,将停止读者继续增加人数使用资源,直到读者一一读完退出为止,所有读者退出后写者立即获得资源并开始写作,这就是所谓的写者优先,实现方法与读者优先其实大致相似,唯一不同的是比读者优先多一个信号量,这个信号量用于就是用于控制写者需要进行写作而停止读者继续增加,因此,分别在读者进程的计数器开头部分加上PV操作,还有写者写作的开头和结尾加上PV操作即可。
源代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define P sem_wait
#define V sem_post
#define equalblock &equalSignal
#define countblock &countBlock
#define srcblock &fileSrc
#define N 10
sem_t equalSignal;
sem_t countBlock;
sem_t fileSrc;
int readercount=0;
int sum=50;
void* Reader(void *p)
{
while(sum)
{
sum--;
P(equalblock); //这个信号量用于当写者需要使用资源时不让读者继续进入
P(countblock); //申请计数器资源
if(readercount==0){
P(srcblock); //申请书籍资源
}
readercount++;
V(countblock); //释放计数器资源
V(equalblock); //如果说写者没有需要而读者得到了这把锁则要释放掉
/** 读者阅读区域 **/
printf("%d位读者正在阅读..
",readercount);
/** 读者阅读区域 **/
P(countblock); //申请计数器资源
readercount--;
if(readercount==0){
V(srcblock); //释放书籍资源
}
V(countblock); //释放计数器资源
}
}
void* Writer(void *p)
{
while(sum)
{
sum--;
P(equalblock);
P(srcblock); //申请书籍资源
printf("写者正在写作ing...
");
V(srcblock); //释放书籍资源
V(equalblock);
}
}
int main()
{
int i;
/** 初始化资源个数 **/
sem_init(equalblock, 0, 1);
sem_init(countblock,0,1);
sem_init(srcblock,0,1);
/** 创建一个写者进程 **/
pthread_t tidw;
pthread_create(&tidw, NULL, Writer, NULL);
/** 创建多个读者进程 **/
for(i=0;i<10;i++){
pthread_t i;
pthread_create(&i, NULL,Reader, NULL);
}
/**观察结果后关闭进程**/
getchar();
pthread_exit(0);
return 0;
}