读者-写者问题
- 概述
- 读者优先
- 写者优先
概述
数据库的管理通常涉及到数据的读取与写入,为数据库访问建立模型,可以允许同时读,但凡有一个在写其他的都不能读取,那么该如何实现呢?
读者优先
我们用一个场景来模拟:
数据库大厅有一个柜台,柜台内仅有一位柜员,这个柜员只上过小学一年级,只会加减法,有读者来了就加一、有读者走了就减一,如果柜台空闲他随机叫醒一位等待使用柜台的读者,他还知道现在是否有人占用数据库,并在数据库空闲时随机叫醒一位等待使用数据库的人。
想要读取数据的读者先要到柜台登记,如果柜台被人占用他就睡觉直到被叫醒。第一个读者负责把数据库打开,读取完数据后要再去柜台缴费结算并离开,最后一个读者负责关闭数据库,只要有读者来,数据库就不会关,直到所有的读者都读完了。
想要写数据的写者要先看看数据库有没有被读者或其他写者占用,如果占用了就小睡一会,直到被叫醒。
这就是现在这个数据库的管理规则,下面用C代码实现:
typedef int semaphore;
semaphore mutex = 1;//控制对rc的访问
semaphore db = 1;//控制对数据库的访问
int rc = 0;//正在读取的进程数
void reader(void)
{
while(1)
{
down(&mutex); //看看readcounter柜台是否被人占用
rc++; //在readcounter(柜台)处注册登记以便等待读取
if(rc == 1)down(&db); //第一个读者打开数据库:数据库不能被修改
up(&mutex); //柜台使用完毕
read_data_base();
down(&mutex); //看看readcounter柜台是否被人占用
rc--; // 柜台没人占用,缴费离开(把名字划掉)
if(rc == 0)up(&db); //最后一个读者关上数据库:表示没有人使用了
up(&mutex); //柜台使用完毕
use_data_read(); //用户处理读取到的数据
}
}
void writer(void)
{
while(1)
{
think_up_data(); //生产将要更新的数据
down(&db); //数据库是否被人占用?
write_data_base(); //没人占用修改数据
up(&db); //数据库使用完毕
}
}
停,请大家思考上面的代码有什么弊端! 停,请大家思考上面的代码有什么弊端! 停,请大家思考上面的代码有什么弊端! 停,请大家思考上面的代码有什么弊端! 停,请大家思考上面的代码有什么弊端!
是不是写者很卑微呀,有种读者优先的意味,这可能导致写者一觉不醒了!
写者优先
Courtois等人整出了一种写者优先的算法,但是《现代操作系统》书中并没有详细介绍,我去站内搜了一下,找到了这篇论文的翻译,读者和写者(Courtois 1971)论文翻译.在此感谢这位老哥的翻译。
论文中给出了伪代码和一些解释,我用更通俗易懂的方式来呈现,还是按照上面的逻辑来讲解,如有错误欢迎探讨:
数据库大厅的门口有一个保安*(mutex3):保安不管写者,只管读者,当大厅内有一个读者在进行读取登记时,其他读者不可进入
数据库大厅内有两个柜台(mutex1、mutex2):readcounter柜台负责读者读取登记和缴费登记,writercounter柜台负责写者写入登记和缴费登记。柜台在访客登入和登出时查看公告板,叫醒相应的人
数据库大厅有一个公告板(write、read):公告板负责显示当前数据库的使用状态,如读者正在读取,写者正在写入,有读者等待,有写者等待
typedef int semaphore;
semaphore mutex1 = 1;//控制对rc的访问
semaphore mutex2 = 1;//控制对wc的访问
semaphore mutex3 = 1;//控制进入大厅的读者数量
semaphore read = 1;//读者对数据库的控制
semaphore write = 1;//写者对数据库的控制
int rc = 0,wc = 0;//读者和写者的计数器
void reader(void)
{
while(1)
{
//大厅入口咨询保安
down(&mutex3); //查看是否可以进入大厅(大厅里只能有一个读者睡觉)
//进入大厅查看公告板
down(&read); //查看是否有写者在工作或睡觉,有写者工作或睡觉则睡觉,直到被叫醒
//前往柜台
down(&mutex1); //看看readcounter柜台是否被人占用
rc++; //在readcounter(柜台)处注册登记以便等待读取
if(rc == 1)down(&write); //第一个读者声明,数据库被读者占用
up(&mutex1); //readcounter柜台使用完毕
up(&read); //若有写者因read信号量在睡觉,叫醒他
up(&mutex3); //若大厅外有读者等待,随机叫一个读者进入大厅
read_data_base();
down(&mutex); //看看readcounter柜台是否被人占用
rc--; // 柜台没人占用,缴费离开(把名字划掉)
if(rc == 0)up(&write); //最后一个读者声明,数据库可以修改了,如有写者因write在睡觉,叫醒他
up(&mutex); //柜台使用完毕
use_data_read(); //用户处理读取到的数据
}
}
void writer(void)
{
while(1)
{
think_up_data(); //生产将要更新的数据
//前往柜台
down(&mutex2); //看看writecounter柜台是否被人占用
wc++; //在readcounter(柜台)处注册登记以便等待读取
if(wc == 1)down(read); //若是第一个写者,查看是否有读者在登记,有则睡觉,直到被叫醒
up(&mutex2); //writecounter柜台使用完毕,离开柜台
//查看公告板
down(&write); //数据库是否被人占用?未被占用则声称占用,被占用则睡觉
write_data_base(); //没人占用修改数据
up(&write); //数据库使用完毕,声称使用完毕
down(&mutex2); //看看writecounter柜台是否被人占用
wc--; // 柜台没人占用,缴费离开(把名字划掉)
if(wc == 0)up(read); //最后一个写者工作完成,查看是否有读者因read睡觉,有则叫醒
up(&mutex2); //writecounter柜台使用完毕,离开柜台
}
}
这样可以保证,当最后一个读者读取完成时,他唤醒的一定是写者,并且当有写者到来并等待的时候,后来的读者无法完成登记。
因为写者一来就会执行一次down(&read);,这样后来的读者也只能被迫等待,而不可能再有其他读者进入。而当之前所有的读者读完后会执行一次up(&write),这时写者开始写了,只有所有的写者都写完才会执行up(read);,这时刚刚等待的读者才能有继续登记的权限。