经典同步问题(三)---读者写者问题
2015-08-12 03:06
211 查看
[b]1. 问题描述[/b]
有读者和写者两组并发线程,共享一个数据库,当两个或以上的读线程同时访问共享数据时不会产生副作用,但若某个写线程和其他线程(读线程或写线程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:
允许多个读者可以同时对文件执行读操作;
只允许一个写者往文件中写信息;
任一写者在完成写操作之前不允许其他读者或写者工作;
写者执行写操作前,应让已有的读者和写者全部退出。
也就是说,读进程不排斥其他读进程,而写进程需要排斥其他所有进程,包括读进程和写进程。
[b]2. 问题分析[/b]
关系分析:由题目分析可知,读者和写者是互斥的,写者和写者也是互斥的,而读者和读者不存在互斥问题。
整理思路:写者是比较简单的,它与任何线程互斥,用互斥信号量的 PV 操作即可解决。读者的问题比较复杂,它必须实现与写者的互斥,多个读者还可以同时读。解决方案有两种:读写优先与写者优先。
读写优先:只要有一个读者处于活动状态(正在读),后来的读者即使比写者后到也会被接纳。如果读者源源不断地出现的话,那么写着就始终处于阻塞状态,直到所有读者读完为止。
写者优先:一旦写者就绪,那么写着会尽可能快地执行写操作。如果写者源源不断地出现的话,那么读者就始终处于阻塞状态。
[b]3. 使用信号量实现[/b]
3.1 读者优先
读进程只要看到有其他读进程正在访问文件,就可以继续作读访问;写进程必须等待所有读进程都不访问时才能写文件,即使写进程可能比一些读进程更早提出申请。可以使用一个计数器rcount记录读者总数目(包含等待和正在读的数目),如果rcount大于0则写者等待,而读者直接读。当rcount==0写者与写者、写者与第一个读者抢占读写操作,这可以用一个二元信号量wt进行互斥访问。因为多个读者线程都要访问计数器,则使用一个二元信号量mutex进行互斥访问。
对于读者优先,在读访问非常频繁的场合,有可能造成写进程一直无法访问文件而饿死的局面。
3.2 写者优先
使用一个计数器wcount记录当前写者的总数据(包含等待和正在写的线程的数据)。当wcount>0,使用信号量read禁止所有的读进程。为了对wcount进行互斥访问,需要使用一个二元信号量wtmutex。
如果当前有读者正在写,写者需要阻塞,所以读者需要定义一个计数器rcount来记录当前正在读的线程数目,当rcount=0时写者才可以写数据。rdmutex为了对rcount互斥访问。
对于读进程,处理read信号量,还需要一个额外的信号量x,这样防止在read信号量等待着大量的读进程。
reader函数使用信号量x的必要性:假如没有使用信号量x,考虑开始只有写线程,那么后来的读线程就会全部阻塞在P(read)中。当wcount==0,读线程开始转为就绪开始执行,当这时又有写线程,那么写线程就会阻塞在P(read)中,直到前面所有阻塞在read信号量的读线程都执行完。引入信号量x的目的就是:只让一个读线程阻塞在read中,其余所有的线程阻塞在x中,这样即使前面等待了大量的读线程,但由于只有一个线程阻塞在read,那么写线程也会立即执行。
[b]3. 使用条件变量实现[/b]
3.1 写者优先
使用AW表示正在写的线程,WW表示正在等待写的线程。
AR表示正在读的线程,WR表示正在读的线程。
使用锁lock对AW WW AR WR进行互斥访问。
okToread okTowrite为条件变量。
3.2 读者优先
《读者写者问题–使用信号量的读者优先与写者优先程序分析》 给出了写者优先使用信号量x的必要性
操作系统——读者写者问题详解 除给出了读者优先、写者优先,还给出了公平竞争算法。
有读者和写者两组并发线程,共享一个数据库,当两个或以上的读线程同时访问共享数据时不会产生副作用,但若某个写线程和其他线程(读线程或写线程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:
允许多个读者可以同时对文件执行读操作;
只允许一个写者往文件中写信息;
任一写者在完成写操作之前不允许其他读者或写者工作;
写者执行写操作前,应让已有的读者和写者全部退出。
也就是说,读进程不排斥其他读进程,而写进程需要排斥其他所有进程,包括读进程和写进程。
[b]2. 问题分析[/b]
关系分析:由题目分析可知,读者和写者是互斥的,写者和写者也是互斥的,而读者和读者不存在互斥问题。
整理思路:写者是比较简单的,它与任何线程互斥,用互斥信号量的 PV 操作即可解决。读者的问题比较复杂,它必须实现与写者的互斥,多个读者还可以同时读。解决方案有两种:读写优先与写者优先。
读写优先:只要有一个读者处于活动状态(正在读),后来的读者即使比写者后到也会被接纳。如果读者源源不断地出现的话,那么写着就始终处于阻塞状态,直到所有读者读完为止。
写者优先:一旦写者就绪,那么写着会尽可能快地执行写操作。如果写者源源不断地出现的话,那么读者就始终处于阻塞状态。
[b]3. 使用信号量实现[/b]
3.1 读者优先
读进程只要看到有其他读进程正在访问文件,就可以继续作读访问;写进程必须等待所有读进程都不访问时才能写文件,即使写进程可能比一些读进程更早提出申请。可以使用一个计数器rcount记录读者总数目(包含等待和正在读的数目),如果rcount大于0则写者等待,而读者直接读。当rcount==0写者与写者、写者与第一个读者抢占读写操作,这可以用一个二元信号量wt进行互斥访问。因为多个读者线程都要访问计数器,则使用一个二元信号量mutex进行互斥访问。
void writer { while(true) { P(wt); write(); V(wt); } } void reader() { while(true) { P(mutex) //对rcount进行互斥访问 rcount++; if(rcount==1) P(wt) //如果是第一个读者,与写者互斥抢占数据库 V(mutex) read() P(mutex) //对rcount进行互斥访问 rcout--; if(rcount==0) V(wt) //如果是最后一个读者,释放数据库所有权 V(mutex) } }
对于读者优先,在读访问非常频繁的场合,有可能造成写进程一直无法访问文件而饿死的局面。
3.2 写者优先
使用一个计数器wcount记录当前写者的总数据(包含等待和正在写的线程的数据)。当wcount>0,使用信号量read禁止所有的读进程。为了对wcount进行互斥访问,需要使用一个二元信号量wtmutex。
如果当前有读者正在写,写者需要阻塞,所以读者需要定义一个计数器rcount来记录当前正在读的线程数目,当rcount=0时写者才可以写数据。rdmutex为了对rcount互斥访问。
对于读进程,处理read信号量,还需要一个额外的信号量x,这样防止在read信号量等待着大量的读进程。
void reader() { while(true) { P(x) //用来保证阻塞在read信号中排队的读者至多只有一个。其余的阻塞在x上 P(read) //等待是否有写进程 P(rdmutex) //对rcount进行互斥访问 if(rcount ==0) //如果是第一个读操作,与写者互斥抢占数据库 P(wt) rcoutn++; V(rdmutex) V(read) V(x) 读操作 P(rdmutex) //对rcount进行互斥访问 rcount--; if(rcount==0) //如果是最后一个读者,释放数据库所有权 V(wt) V(rdmutex) } } void writer() { while(true) { P(wtmutex) if(0 == wcount) P(read) //与写操作 wcount++; V(wtmutex) P(wt) 写 V(wt) P(wtmutex) wcount--; if(wcount==0) V(read) V(wtmutex) } }
reader函数使用信号量x的必要性:假如没有使用信号量x,考虑开始只有写线程,那么后来的读线程就会全部阻塞在P(read)中。当wcount==0,读线程开始转为就绪开始执行,当这时又有写线程,那么写线程就会阻塞在P(read)中,直到前面所有阻塞在read信号量的读线程都执行完。引入信号量x的目的就是:只让一个读线程阻塞在read中,其余所有的线程阻塞在x中,这样即使前面等待了大量的读线程,但由于只有一个线程阻塞在read,那么写线程也会立即执行。
[b]3. 使用条件变量实现[/b]
3.1 写者优先
使用AW表示正在写的线程,WW表示正在等待写的线程。
AR表示正在读的线程,WR表示正在读的线程。
使用锁lock对AW WW AR WR进行互斥访问。
okToread okTowrite为条件变量。
AR = 0; AW = 0; WR = 0; WW = 0; Condition okToRead; Condition okToWrite; Lock lock; void reader() { while(true) { startread(); //等,直到没有在等待或正在写的写者 read; doneread(); //如果有等待的写者,唤醒写者 } } void startread() { lock.acquire(); while((WW+AW)>0) //如果有在等待或正在写的写者,读者阻塞 { WR++; okToread.wait(&lock); WR--; } AR++; lock.release(); } void doneread() { lock.require(); AR--; if(AR==0 && WW>0) //如果正在读的读者为零且有在等待的写者,唤醒写者 okToWrite.signal(); lock.release(); } void writer() { while(true) { startwrite(); //等,直到没有正在写的读者或写者 write; donewrite(); //如果有正在等待的写者,唤醒,否则如果有正在等待的读者,唤醒 } } void startwrite() { lock.require(); while((AR + AW) >0 ) //如果有正在写的读者或写者,写者等待 { WW++; okTowrite.wait(&lock); WW--; } AW++; lock.release(); } void donewrite() { lock.require(); AW--; if(WW>0) //如果有写者等待,唤醒写者 okTowrite.signal(); else if(WR>0) //如果有读者等待,唤醒读者 okToread.broadcast(); lock.release(); }
3.2 读者优先
AR = 0; AW = 0; WR = 0; WW = 0; Condition okToRead; Condition okToWrite; Lock lock; void reader() { while(true) { startread(); //等,直到没有正在写的写者 read; doneread(); //如果没有读者且有写者等待,唤醒写者 } } void startread() { lock.require(); while(AW>0) //如果有正在写的写者,读者等待 { WR++; okToRead.wait(&lock); WR--; } AR++; lock.release(); } void doneread() { lock.require(); AR--; if((AR+WR)==0 && WW>0) //如果没有读者且有等待的写者,唤醒写者 okTowrite.signal(); lock.release(); } void writer() { while(true) { startwrite(); //wait until no readers/writers write; donewrite(); //checkout-wake up waiting readers/writers } } void startwrite() { lock.require(); while((AR + WR + AW)>0) //如果有读者或正在写的写者,写者等待 { WW++; okTowrite.signal(); WW--; } AW++; lock.release(); } void donewrite() { lock.release(); AW--; if(WR>0) //如果有等待的读者,唤醒读者 okToread.broadcast(); else if(WW>0) //如果有等待的写者,唤醒写者 okTowrite.signal(); lock.release(); }
4. 参考
顶你学堂 操作系统 《第十章 信号量与管程》经典同步问题1-3《读者写者问题–使用信号量的读者优先与写者优先程序分析》 给出了写者优先使用信号量x的必要性
操作系统——读者写者问题详解 除给出了读者优先、写者优先,还给出了公平竞争算法。
相关文章推荐
- HDU1556-Color the ball-线段树成段更新入门题/前缀和
- 以登录实例简介Servlet使用
- 弹丸论破2 中文攻略
- swoole的worker和task区别
- Jsoncpp的使用
- Nim如何在windows下使用winapi创建窗口
- 安卓获取远程值http和saop
- Java 泛型数组 不支持
- asp Win7 IIS7.5配置 ASP
- TP上传图片
- 作为码农 ,我们为什么要写作
- 初次尝试用devc++ 写自定义头文件 遇到的问题
- 句子逆序(循环两次)
- Murano Weekly Meeting 2015.08.11
- _DataStructure_C_Impl:求图G中从顶点u到顶点v的一条简单路径
- iOS delegate
- android事件拦截处理机制详解 .--------转
- _DataStructure_C_Impl:在图G中求距离顶点v0最短路径为k的所有顶点
- Linux网络编程(附1)——封装read、write
- [UVA 1629]Cake slicing[记忆化搜索]