讀者寫者問題

這篇文章參考了該鏈接http://blog.sina.cn/dpool/blog/s/blog_a3eacdb20101ct0c.html

2.讀者—寫者問題 讀者—寫者問題(Readers-Writers problem)也是一個經典的併發程序設計問題,是經常出現的一種同步問題。計算機系統中的數據(文件、記錄)常被多個進程共享,但其中某些進程可能只要求讀數據(稱爲讀者Reader);另一些進程則要求修改數據(稱爲寫者Writer)。就共享數據而言,Reader和Writer是兩組併發進程共享一組數據區,要求:

(1)允許多個讀者同時執行讀操作;
(2)不允許讀者、寫者同時操作;
(3)不允許多個寫者同時操作。
Reader和Writer的同步問題分爲讀者優先、弱寫者優先(公平競爭)和強寫者優先三種情況,它們的處理方式不同。
(1)讀者優先。對於讀者優先,應滿足下列條件:
如果新讀者到:
①無讀者、寫者,新讀者可以讀;
②有寫者等待,但有其它讀者正在讀,則新讀者也可以讀;
③有寫者寫,新讀者等待。
如果新寫者到:
①無讀者,新寫者可以寫;
②有讀者,新寫者等待;
③有其它寫者,新寫者等待。
單純使用信號量不能解決讀者與寫者問題,必須引入計數器rc 對讀進程計數;rc_mutex 是用於對計數器rc 操作的互斥信號量;write表示是否允許寫的信號量;於是讀者優先的程序設計如下:
int rc=0; //用於記錄當前的讀者數量
semaphore rc_mutex=1; //用於對共享變量rc 操作的互斥信號量
semaphore write=1; //用於保證讀者和寫者互斥地訪問的信號量
void reader()
do{
P(rc_mutex); //開始對rc共享變量進行互斥訪問
rc ++; //來了一個讀進程,讀進程數加1
if (rc==1) P(write); //如是第一個讀進程,判斷是否有寫進程在臨界區,
//若有,讀進程等待,若無,阻塞寫進程
V(rc_mutex); //結束對rc共享變量的互斥訪問
讀文件;
P(rc_mutex); //開始對rc共享變量的互斥訪問
rc--; //一個讀進程讀完,讀進程數減1
if (rc == 0) V(write); //最後一個離開臨界區的讀進程需要判斷是否有寫進程//需要進入臨界區,若有,喚醒一個寫進程進臨界區
V(rc_mutex); //結束對rc共享變量的互斥訪問
} while(1)
void writer()
do{
P(write); //無讀進程,進入寫進程;若有讀進程,寫進程等待
寫文件;
V(write); //寫進程完成;判斷是否有讀進程需要進入臨界區,
//若有,喚醒一個讀進程進臨界區
} while(1)
讀者優先的設計思想是讀進程只要看到有其它讀進程正在讀,就可以繼續進行讀;寫進程必須等待所有讀進程都不讀時才能寫,即使寫進程可能比一些讀進程更早提出申請。該算法只要還有一個讀者在活動,就允許後續的讀者進來,該策略的結果是,如果有一個穩定的讀者流存在,那麼這些讀者將在到達後被允許進入。而寫者就始終被掛起,直到沒有讀者爲止。
(2)寫者優先1。爲了解決以上問題,寫者優先1的設計思想是在一個寫者到達時如果有正在工作的讀者,那麼該寫者只要等待正在工作的讀者完成,而不必等候其後面到來的讀者就可以進行寫操作。注意,該算法當一個寫者在等待時,後到達的讀者是在寫者之後被掛起,而不是立即允許進入。
在讀者優先的算法的基礎上增加了一個排隊信號量read,讀、寫進程在每次操作前都要等待read信號量。寫者優先1的程序設計如下:
int rc=0; //用於記錄當前的讀者數量
semaphore rc_mutex=1; //用於對共享變量rc 操作的互斥信號量
semaphore write=1; //用於保證讀者和寫者互斥地訪問的信號量
semaphore add_reader=1; //該信號量記錄是否允許新來的讀者添加到rc(既使得rc++)
void reader()
do{
P(add_reader); //該操作允許後面的rc++,也就是說此時來的讀者可以和前面的讀者一起讀操作
P(rc_mutex); //開始對rc共享變量進行互斥訪問
rc++; //來了一個讀進程,讀進程數加1
if rc=1 then P(write); //第一個讀進程需要判斷是否有寫進程在臨界區,若有,
//讀進程需要等待,若沒有,阻塞寫進程
V(rc_mutex); //結束對rc共享變量的互斥訪問
V(add_reader); //允許後續的writer跟read競爭add_reader,其實該操作加在P(add_reader);後面應該是沒有問題的,因爲這時候說明該讀者已經有資格加入讀操作隊列裏面了,不用等待,這時候的add_reader資源可以讓後面的讀者寫者自己去競爭了。
Reading the file;
P(rc_mutex); //開始對rc共享變量的互斥訪問
rc--; //一個讀進程讀完,讀進程數減1
if rc=0 then V(write); //最後一個離開臨界區的讀進程需要判斷是否有寫進程
/ //需要進入臨界區,若有,喚醒一個寫進程進臨界區
V(rc_mutex); //結束對rc共享變量的互斥訪問
}
void writer()
do{
P(add_reader); //不允許後面的rc++,也就是說此時來的讀者不可以和前面的讀者一起讀操作,不能再往隊列裏面加了,必須等着我完成了,你們才能讓rc++。這時候的只等着獲取rc_mutex,就可以執行了。
P(rc_mutex); //開始對rc共享變量進行互斥訪問

//到來的讀進程排在該隊列寫者之後
P(write); //若有讀進程在讀,等待現有讀進程讀完纔可寫
Writeing the file;
V(write); //寫進程完成;判斷是否有讀進程需要進入臨界區,若有,
//喚醒一個讀進程進臨界區
V(add_reader); //允許後面的rc++,我已經執行完了,這時候我後面來的讀者可以競爭我釋放的add_reader資源了
注意,該算法當第一個寫者已經P(add_reader)後,add_reader變爲0,來了N個讀者,他們都停留在它的P(add_reader)這一句。那麼會出現什麼問題呢?此時,如果原來的寫者完成了,緊接又來了一個寫者,寫者需要P(read)。這個時候,由於N個讀者都已經在這個寫者之前P(read)了,所以這個寫者需要排隊排在這N個讀者分別都得到P(read)後才能得到執行,這個就不是寫者優先了,而是讀者寫者公平競爭。
(3)寫者優先2。爲了保證寫者相對於讀者的優先,需要提高寫者進程的優先級。這裏除增加一個排隊信號量read,讓讀者和寫者在讀寫之前都在此信號量上排隊。還需增加一個信號量write_first,來保證寫者優先。寫者優先2的程序設計如下:
int rc, wc = 0; //用於讀者,寫者的計數
semaphore add_reader, write = 1; //用於讀進程和寫進程的互斥信號量
semaphore rc_mutex, wc_mutex, write_first = 0;//用於讀時、寫時和寫者優先的互斥
void reader(){
while(1){
P(write_first); //用來保證寫者優先。只有寫者釋放了信號量,這裏才能獲得
P(add_reader); //競爭add_reader資源,如果被一批寫進程獲取了,就需要寫進程就緒隊列全部執行過了,纔有機會獲得add_reader,
P(rc_mutex); //開始對rc共享變量進行互斥訪問
rc++; //更新讀進程數
if (rc==1) P(write); //第一個讀進程需要判斷是否有寫進程在執行寫操作,不然的話大家可以一起讀
V(rc_mutex); //結束對rc共享變量的互斥訪問
V(add_reader); //從 read隊列中喚醒一個進程,還是同樣的,這句話應該是可以放在P(add_reader)後面的
V(write_first);//允許後面的來讀
doReading();
P(rc_mutex);//開始對rc共享變量進行互斥訪問
rc--; //更新讀進程數量
if (rc==0) V(write); 
V(rc_mutex); //結束對rc共享變量的互斥訪問
}
}
void writer(){

while(1){

P(wc_mutex); //開始對wc共享變量進行互斥訪問

wc++; //更新寫進程數
if (wc==1) P(add_reader); //第一個寫進程有必要阻止後面的讀者加入到就緒讀者隊列,需要讓後續的讀者加入到競爭add_reader的等待隊列
V(wc_mutex); //結束對wc共享變量的互斥訪問
P(write); //限制同一時刻只能有一個寫進程進行寫操作
do writing();
V(write); //結束對寫操作的限制
P(wc_mutex); //開始對wc的互斥訪問
wc--; //更新寫進程數量
if (wc==0) V(add_reader); //最後一個進程需要提醒在add_reader等待隊列的讀者開始競爭add_reader,注意和上面(2)不同的地方在於這裏是提醒在add_reader等待隊列的讀者開始競爭add_reader,而不是提醒所有在等待隊列的讀者和寫者去競爭add_reader

V(wc_mutex); //結束對wc的互斥訪問

V(writer_first);//爲了保證寫操作優先,先要釋放至少一個writer_first資源,後面的讀者才能讀取

}
}
這裏write_first的設立是爲了保證寫者優先。因爲write_first的初值是0,只有第一個寫者寫完之後,纔可以進行讀操作。在讀進程,執行完P(write_first)後等在P(read)這一句的讀者最多隻有1個。
對於read信號量,每個讀進程最開始都要申請一次,之後在真正做讀操作前即讓出,這使得寫進程可以隨時申請到 read。而寫進程,只有第一個寫進程需要申請 read,之後就一直佔着不放了,直到所有寫進程都完成後才讓出。等於只要有寫進程提出申請就禁止讀進程在read信號量上排隊。
假設一個寫進程正在寫時,接着後續有n個讀者正在等待,這時又有一個新的寫者要寫,比較一下寫者優先1和寫者優先2的情況:寫者優先1新來的寫者需要等待n+1次V(read)操作,而寫者優先2新來的寫者只需等待2次V(read)操作,變相提高了寫進程的優先級。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章