日期:2019/4/15
內容:進程同步;生產者與消費者;讀寫者;哲學家進餐;信號量機制。
一、生產者與消費者問題
1.1 版本1
- 代碼
void producer() { while (count == n) ; buff[in] = produce_item(); in = (in + 1) % n; count++; } |
void consumer() { while (count == 0) ; item = buff[out]; print(item); out = (out + 1) % n; count--; } |
-
存在問題
>>兩個while循環一直在"忙等",不符合進程同步的"讓權等待"原則。
>>對於count變量的訪問沒有保護。(需要加鎖保護)
1.2 版本2:使用信號量
- 代碼
semaphore empty = n, full = 0; void producer() { while (true) { wait(empty); buffer[in] = produce_item(); in = (in + 1) % n; signal(full); } } |
void consumer() { while (true) { wait(full); item = buffer[out]; print(item); out = (out + 1) % n; signal(empty); } } |
-
存在問題
>>如果有2個producer進程,empty>=2時,同時進入wait(empty)之后的臨界區,對於buff的寫和in的寫產生競爭。
>>如果有2個consumer進程,full>=2時,同時進入wait(full)之后的臨界區,對於out的寫產生競爭。
1.3 版本3:臨界區加鎖(正確版本)
- 代碼
semaphore pmutex = 1, cmutex = 1; semaphore empty = n, full = 0; void producer() { while (true) { wait(empty); wait(pmutex); buff[in] = produce_item(); in = (in + 1) % n; signal(pmutex); signal(full); } } |
void consumer() { while (true) { wait(full); wait(cmutex); item = buff[out]; print(item); out = (out + 1) % n; signal(cmutex); signal(empty); } } |
注:教材對於producer和consumer的臨界區都使用了同一個mutex,表示producer和consumer互斥進入臨界區。但是個人感覺似乎沒必要,因為producer和consumer對於buff的訪問不存在競爭關系,只需要保證多個producer進程之間互斥,多個consumer進程之間互斥即可。
二、讀者與寫者問題
對於多個進程訪問同一文件:
- 寫者:可以讀,也可以寫
- 讀者:只讀
- 允許多個讀者同時讀
- 某一寫者在寫,不允許其他讀寫操作
R |
R |
1 |
R |
W |
0 |
W |
R |
0 |
W |
W |
0 |
與生產者消費者的區別:
- 生產者不僅僅是寫進程,還必須調整in指針,但是在讀寫者問題當中每個進程的文件讀寫指針是相互獨立的。
- 消費者同理。
2.1 版本1
- 代碼
semaphore mutex = 1; void writer() { while (true) { wait(mutex); write_operation(); signal(mutex); } } |
void reader() { while (true) { wait(mutex); read_operation(); signal(mutex); } } |
-
問題
>>不滿足"同時讀"。
2.2 版本2:增加讀者計數器
- 代碼
semaphore wmutex = 1; void writer() { while (true) { wait(wmutex); write_operation(); signal(wmutex); } } |
void reader() { while (true) { if (reader_count == 0) wait(wmutex); reader_count++;
read_operation();
reader_count--; if (reader_count == 0) signal(wmutex); } } |
-
問題
>>可以允許多個reader進程同時讀,但是對reader_count的訪問存在競爭。
2.3 版本3:給reader_count加鎖
rmutex實際上只用於保護reader_count被正確更新。
- 代碼
semaphore wmutex = 1, rmutex = 1; int reader_count = 0; void writer() { while (true) { wait(wmutex); write_operation(); signal(wmutex); } } |
void reader() { while (true) { if (reader_count == 0) wait(wmutex); wait(rmutex); reader_count++; signal(rmutex);
read_operation();
wait(rmutex); reader_count--; signal(rmutex); if (reader_count == 0) signal(wmutex); } } |
-
問題
>>舉例說明紅色部分代碼問題,reader1和reader2同時執行,同時讀取reader_count均為0,那么(假設)先執行reader1的wait1(wmutex),reader1進入阻塞隊列;后執行reader2的wait2(wmutex),reader進入阻塞隊列。(wait是原語操作,1和2必有先后之分)。但是在reader2執行的時候reader_count的值應為1(但實際是0),這就會使reader2成為僵死進程。
2.4 正確版本1:讀者優先
- 代碼
semaphore wmutex = 1, rmutex = 1; int reader_count = 0; void writer() { while (true) { wait(wmutex); //保證了W與W互斥 write_operation(); signal(wmutex); } } |
void reader() { while (true) { wait(rmutex); //保證只能有一個reader訪問reader_count if (reader_count == 0) wait(wmutex); reader_count++; signal(rmutex);
read_operation();
wait(rmutex); //保證只能有一個reader訪問reader_count reader_count--; if (reader_count == 0) signal(wmutex); signal(rmutex); } } |
-
問題
>>當讀者進程≥1時,隨后讀者進程直接進入臨界區,這是讀者優先的表征。
>>寫者餓死問題。
>>假設有進程{R1, W1, R2, R3, ..., Rn}
>>>>執行R1,那么wmutex變為0,執行read_operation
>>>>執行W1,wmutex變為-1,阻塞W1
>>>>執行R2,wmutex不變,執行read_operation
>>>>對於若干Ri,均是如此,如果CUP資源不足,Ri會進入就緒隊列
>>>>那么W1則很長時間無法調度,就算被siganl操作移入就緒隊列也是在隊列尾部,產生寫者餓死問題。
2.5 正確版本2:寫者優先
解決寫者餓死問題:保證一個寫進程想寫時(即使它有可能進入阻塞隊列),不允許新的讀進程訪問臨界區。(注意這並不是為了解決"讀和寫不能同時進行")。上面讀者優先的症結在於寫進程想寫,但是讀進程優先,不斷地進入臨界區,即讀的調度優先級比寫高,從而導致讀者餓死。
- 代碼
int reader_count = 0, writer_count = 0; semaphore x = 1, y = 1, z = 1; semaphore wmutex = 1, rmutex = 1; void writer() { while (true) { wait(y); if (writer_count == 0) wait(rmutex); writer_count++; signal(y);
wait(wmutex); write_operation(); signal(wmutex);
wait(y); writer_count--; if (writer_count == 0) wait(rmutex); signal(y); } } |
void reader() { while (true) { wait(z); wait(rmutex); wait(x); if (reader_count == 0) wait(wmutex); reader_count++; signal(x); signal(rmutex); signal(z);
read_operation();
wait(x); reader_count--; if (reader_count == 0) wait(wmutex); signal(x);
} } |
-
解析
- x:控制reader_count的訪問競爭。
- y:控制writer_count的訪問競爭。
- wmutex:控制有寫進程在寫的時候,讀進程不能進入臨界區。
- rmutex:寫進程先把rmutex拿到,保證在寫進程運行時,其他所有讀進程均無法進入臨界區(只能阻塞在rmutex隊列上,見紅色代碼)。
- z:保證了在rmutex的阻塞隊列上,只有一個讀進程在排隊,其余所有讀進程在等待rmutex之前,在z的隊列上排隊。(嘗試把wait(z)和signal(z)去掉理解一下)如果沒有z,則都在rmutex上排隊。
- 為什么需要z?在rmutex上不允許長的排隊,否則寫進程不能跳過這個長隊列。
-
舉例說明:{R1, W1, R2, R3, ..., Rn}
- 如果有z,reader1先wait1,rmutex=0;然后writer再wait(rmutex),rmutex=-1;后面盡管有再多的reader都在堵塞在z。此時,只需要等待reader1執行signal(rmutex),writer即能夠進入就緒狀態,優先於z中阻塞的reader。
- 如果無z,writer和所有的reader都進入rmutex排隊,實質上無法保證writer優先於reader。
三、哲學家進餐問題
-
問題描述
哲學家需要只吃飯和思考,需要用2把叉子才能吃飯。叉子只能用自己座位兩側的。需要避免死鎖和飢餓。
3.1 版本1
- 代碼
semaphore fork[5] = {1, 1, 1, 1, 1}; void philosopher(int i) { while (true) { think(); wait(fork[i]); wait(fork[(i + 1) % 5]); eat(); signal(fork[i]); signal(fork[(i + 1) % 5]); } }
void main() { for (int i = 0; i < 5; i++) create_process(philosopher(i)); } |
-
問題
>>死鎖。每個人同時拿起自己左邊的叉子,即philosopher[i]拿起fork[i],那么每個人在wait(fork[(i + 1) % 5])上都會阻塞。
3.2 版本2(正確版本)
解決方案:同時只允許4個人進入房間就餐,那么即至少能保證有1人可以拿到2個fork。
- 代碼
semaphore fork[5] = {1, 1, 1, 1, 1}; semaphore room = 4; void pholosopher(int i) { think(); wait(room); wait(fork[i]); wait(fork[(i + 1) % 5]); eat(); signal(fork[i]); signal(fork[(i + 1) % 5]); signal(room); } void main() { for (int i = 0; i < 5; i++) create_process(philosopher(i)); } |
-
解析
保證不會死鎖和飢餓。(管程解決方案待續)