1. 程式人生 > >挑戰408——作業系統(11)——多角度看生產者消費者問題

挑戰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. 在一個寫操作結束的時候,所有等待的讀者必須比下一個寫者有更高的優先權。