挑戰408——操作系統(11)——多角度看生產者消費者問題

對於信號量的使用,主要是如何選擇信號量和如何安排pv操作在程序中的位置。下面來分析一下經典的生產者消費者問題。教材的說法有點過於籠統了,我就用自己的語言來重新描述一遍。

生產者——消費者問題

問題描述:有兩組進程共享一個環形的緩衝池,其中的一組進程稱爲生產者,另一組進程稱爲消費者。緩衝池由若干個大小相等的緩衝區組成,每一個緩衝區都可以容納一個產品。生產者進程不斷的將生產的產品放入到緩衝池中,消費者進程則不斷的將產品從緩衝池中取出。指針 i 和 j 分別指向第一個空閒的緩衝區和第一個裝滿的緩衝區。(陰影部分表示滿,空白部分表示空)
在這裏插入圖片描述

問題分析
解決這類問題,關鍵是要理清楚,誰是臨界資源?進程間存在什麼交互關係?應該如何設置信號量?如何安排PV操作的順序

  1. 臨界資源分析:緩衝池中的緩衝區是臨界資源(就是圖中一小塊一小塊的東西),它一次只允許一個生產者放入產品或者一個消費者從中取出產品消費。
  2. 交互關係分析:生產者訪問緩衝區的時候,消費者不能訪問,當然,生產者之間也不能訪問同一個緩衝區。反之亦然,故而兩者間存在訪問緩衝區時,存在互斥的關係,但是生產者必須在消費者之前進行(不然你消費什麼),因此也存在同步關係。
  3. 思路分析:這裏面只有兩種進程,關係上只有同步和互斥,因此只要PV操作併合理安排其位置就可以解決此類問題。
  4. 信號量設置:因爲存在互斥的問題,所以先設置互斥信號量 mutex = 1。用於控制兩個進程對緩衝區的互斥訪問,再設置一個信號量full,用於記錄當前緩衝池中已經滿的緩衝區數,剛剛開始的時候,生產者還沒有生產,因此初始值爲0.而信號量empty,用來記錄當前緩衝區中空的緩衝區數,初始值爲n,於是我們很容易看出來,full + empty = n始終成立。

生產者——消費者僞代碼分析如下:

//生產者消費者問題
//設置信號量的初始值
semaphore mutex = 1;
semaphore empty = n,full = 0;
int i,j;//設置指針

//生產者進程
void producer(){
	while(true){
		生產一個數據;
		P(empty);//申請一個空白的區域用來存放生產的數據,此時empty - 1
		//申請完了之後,互斥進入緩衝區,使得其他進程不能訪問該緩衝區
		p(mutex);//mutex = mutex - 1 = 0
		將數據放入緩衝區;
		V(mutex);//退出緩衝區,互斥信號量恢復爲1(即mutex = mutex+1)
		V(full);//此時,緩衝池中的滿的緩衝區 +1 
		        //對於消費者來說,這就是釋放了資源
	}
}

void consumer(){
	while(true){
		P(full);//申請一塊滿的緩衝區
		P(mutex);//互斥進入
		將數據從緩衝池中取出來;
		V(mutex);//互斥退出
		V(empty);//釋放緩衝區,此時緩衝區狀態爲空
		         //緩衝池中的空的緩衝區 +1
		消費取出的數據。
	}
}

這裏注意一下,消費者進程是先取出來再消費的。我們稱用來表示互斥的信號量爲互斥信號量,代表可使用的資源量稱爲資源信號量,那麼我們應該先對資源信號量進行P操作後,才能對互斥信號量進行P操作
那如果我們反起道而行之呢?分析一下。先執行p(mutex)再執行P(empty);。倘若此時,生產者已經將緩衝池放滿,消費者並沒有來取產品(即empty = 0);下次仍然是生產者運行,它先執行p(mutex),被阻塞,希望消費者取出產品後將其喚醒,但是這個時候,由於先執行的是p(mutex),mutex的值爲0,信號量被封鎖,消費者進程進不去臨界區,因而被阻塞。這樣雙方都指望對方喚醒自己,然後又都陷入阻塞。因而陷入無休止的等待。這種狀態我們稱爲死鎖。(後面詳細介紹)

讀者——寫者問題

問題描述:一個數據文件被多個併發進程所共享,其中一些進程只要求讀取文件的內容,而一些進程則要求對文件內容進行修改。我們稱前者爲讀者,後者爲寫者。因爲讀者並不改變文件的內容,所以我們允許多個進程同時訪問,而寫者不同,他們要改變數據對象中的內容,因此一隻能允許一個寫者進程,並且寫的時候也不能有讀者進程進行讀取。我們把限制條件羅列一下:

  1. 允許任意多個進程同時進行讀操作
  2. 一次只能允許一個寫進程進行寫操作
  3. 若有一個寫進程進行寫,那麼所有進程都不能對文件操作(包括讀)
  4. 寫者在執行寫操作的時候,應該要求已經在對文件進行操作的讀,或者寫進程退出。

問題分析

  • 臨界資源分析:顯然被訪問的文件是臨界資源
  • 交互關係分析:寫者與所有的進程都互斥,讀者與讀者都不互斥
  • 思路分析:對於寫者而言,它與所有的進程都互斥,用簡單的PV操作就可以完成。但是讀者的問題較爲複雜,它除了要與寫者之間實現互斥,還要實現與其他寫者的同步(因爲寫者要在讀者離開以後才能寫)。那麼,設置一個計數器,用來判斷當前是否有讀者在讀文件。有則加1,那麼這個變量對於讀者來說是個共享的變量,人人都可以訪問,所以讀者間也要互斥的訪問。
  • 信號量的設置:現在很明確,我們要設置信號量Rcount,用來記錄當前讀者的數量,初始值爲0,設置Rmutex爲互斥信號量,初始值爲1,用於讀者之間對計數器的互斥訪問,設置Wmutex,用於寫者之間的互斥訪問,初始值爲1。

實現的僞代碼如下:


semaphore Wmutex,Rmutex = 1;//互斥信號量
int Rcount = 0;
//讀者進程
void reader(){
	while(true){
		P(Rmutex);//讀者進程互斥訪問計數器變量
		//如果此時沒有讀者進程,那麼先申請Wmutex,使它 =0
		if(Rcount == 0) P(Wmutex);//這樣所有的寫進程都被阻塞
		Rcount ++;//否則讀者進程數 +1,表明現在多了一個讀者進程
		V(Rmutex);//釋放計數器信號量
		..............
		...讀取文件...
		..............
		P(Rmutex);//每次訪問Rcount都是以互斥的形式
		Rcount--;//讀完文件後,進程退出,計數器減一
		//如果此時沒有讀者進程,那麼喚醒寫者進程,允許寫
		if(Rcount == 0)V(Wmutex);
		V(Rmutex);//釋放互斥信號量
		
	}
}

//寫者進程
void writer(){
	while(true){
		P(Wmutex);
		............
		..寫入文件..
		............
		V(Wmutex)
	}
}

上面的代碼是一種讀者優先的策略。那麼哪裏體現了優先呢?我們看看讀者進程中的這兩句代碼:

	P(Rmutex);//讀者進程互斥訪問計數器變量
		//如果此時沒有讀者進程,那麼先申請Wmutex,使它 =0
		if(Rcount == 0) P(Wmutex);//這樣所有的寫進程都被阻塞

若讀者計數器爲0,那麼這時候可能有這麼一種情況,讀者寫者進程同時要求訪問這個文件,但是他們不能同時訪問,因爲讀的時候不能寫,寫的時候不能讀。所以,上面的做法是 P(Wmutex),將寫者進程的信號量申請掉,也就是Wmutex = 0,那麼寫者進程由於再P的話就會進入阻塞狀態。相當於讓步給讀者進程進行操作。帶操作完成後再V(Wmutex),喚醒寫者進程進行操作。

因此我們換個角度,也可以讓寫者優先,我就具體的不說了,原理同上。
但是注意:無論是讀者優先還是寫者優先,當優先級較高的進行操作的時候,那麼優先級較低的就必須等待。如果後面來的都是優先級較高的操作,那麼致歉優先級較低的就會被無限期的掛起,造成飢餓現象
於是一種公平策略的做法就產生了,具體的我就不寫了,寫個規則,下篇文章貼出來。

  1. 在一個讀序列中國,若有寫者等待,那麼就不允許新來的讀者開始執行
  2. 在一個寫操作結束的時候,所有等待的讀者必須比下一個寫者有更高的優先權。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章