如何使用信號量處理問題

從信號量的理論跨越到對其應用解決實際的問題,對於我這種人來說,是一種脫離地心引力飛向太陽系的體驗。

以下有7個問題(不寫目錄了)。

1、

生產者---消費者問題

有一個或多個生產者生產某種類型的數據(記錄、字符),並防止在緩衝區中;有一個消費者從緩衝區中取數據,每次取一項;系統保證避免對緩衝區的重複操作,也就是說,在任何時候只有一個主體(生產者或者消費者)可以訪問緩衝區。問題是要確保這種情況,當緩衝區已滿時,生產者不會繼續向其中添加數據;當緩衝區爲空時,消費者不會從中移走數據。

1)問題分析:

從題中可以發現需要對緩衝區進行同步互斥操作,生產者爲消費者生產必要產品放入緩衝區,消費者爲生產者生產空閒緩衝區,這就需要同步操作;對於同一個緩衝區而言,只能有一個生產者或消費者訪問,也就是生產者與生產者之間、生產者與消費者、消費者與消費者需要互斥操作。

對於生產者:

判斷緩衝區是否爲滿,如果爲滿,則等待;否則,允許一個生產者寫入。

對於消費者:

判斷緩衝區是否爲空,如果爲空,則等待;否則,允許一個消費者讀取。

2)僞碼描述:

semaphore buffer, empty, full;//分別表示緩衝,緩衝剩餘資源,緩衝已用資源的信號量
buffer.v = 1;//表示緩衝空閒
empty.v = MAX_SIZE;//表示緩衝爲空
full.v = 0;//表示緩衝爲空
void Producer()
{
	while(isProduce()) {
		//produce	
		P(empty);//判斷是否有空閒的緩衝
		P(buffer);//判斷緩衝是否佔用
		append();//將產品放入緩衝中
		V(buffer);//釋放對緩衝的使用
		V(full);//緩衝中添加一塊產品
	}
}
void Consumer()
{
	while(isConsume()) {
		P(full);//判斷是否有非空的緩衝
		P(buffer);//判斷緩衝是否佔用
		take();//取走產品
		V(buffer);//釋放對緩衝的使用
		V(empty);//多出一塊空閒緩衝區
		//consume
	}
}

2、

哲學家進餐問題

有5位哲學家住在一座房子裏,在他們面前有一張餐桌。每位哲學家的生活就是思考和吃飯。所有的哲學家的食物是意大利麪條,由於缺乏手工技能,每位哲學家需要兩把叉子進餐。一個圓盤上有一大碗麪,每位哲學家面前有一個盤子,左手邊有一個叉子。每個想吃飯的哲學家將坐到桌子旁分配給他的位置上,使用盤子兩側的叉子,取面和吃麪。

1)問題分析:

題中的叉子顯然是一個互斥資源,對於一個叉子而言,如果有一個哲學家佔用,那麼另一個哲學家只能等待。

對於每一個哲學家來說,

他需要判斷能否申請到手邊的兩個叉子,如果只能得到一個或者沒有得到叉子,只能等待;否則,可以進餐。

考慮到會出現飢餓的現象,比如:同時拿起左手邊的叉子,所有哲學家同時等待。

爲了解決這個問題,可以使用一個信號量表示餐桌上最多坐四人,第5位哲學家想坐時,只能等待。

2)僞碼描述:

semaphore forkLeft, forkRight, people;//左手邊的叉子,右手邊的叉子,允許使用桌子的人數
forkLeft.v = forkRight.v = 1;//表示兩手邊的叉子都處於空閒
people.v = 4;//允許桌子的人數
void philosopherX()
{
	P(people);//第X位哲學家申請使用桌子,桌邊允許人數減少一個人
	P(forkRight);//申請使用右手邊的叉子
	P(forkLeft);//申請使用左手邊的叉子
	//eat
	V(forkRight);//釋放兩手的叉子
	V(forkLeft);
	V(people);//離開桌子,桌邊允許人數加一
}

3、

理髮師睡眠問題

理髮師店裏有一位理髮師、一把理髮椅和N把在等候區的椅子、M個希望理髮的顧客。沒有客人,理髮師便在理髮椅上睡覺。但一個顧客到來時,必須叫醒理髮師或者等待正在理髮的理髮師。需要等待時如果等待區的椅子可坐,就坐下等待;否則離開理髮店。

1)問題分析:

此處的理髮椅,需要對其進行互斥操作,保證理髮師和顧客之間,顧客和顧客之間只有一人使用,其中理髮師與顧客之間需要處理同步問題,顧客與顧客之間需要處理互斥問題。

同時對於等候區的N把椅子,對於顧客來說也是競爭資源,需要信號量處理。但是考慮到坐不下就需要離開理髮店,發現單純使用信號量並不能很好的控制,需要一個計數器,來表示剩餘多少座位可以坐下,如果不能坐,則離開,該計數器有互斥信號量控制。

對於理髮師,如果沒有理髮,並且沒有人需要理髮,則在理髮椅上睡覺,否則會去理髮。同時需要記錄等待區流動的人數。

對於顧客,先判斷是否有位子坐,如果有位子,在判斷是否能輪到他理髮,如果輪到他理髮,那麼理髮。同樣需要記錄等待區流動的人數。

2)僞碼描述:

semaphore workingBarber,  waitingClient, waitingRoom;//表示理髮師,顧客,等待區的信號量
int emptySeats = N;//等待區空位數
workingBarber.v = waitingClient.v = 0;//理髮師並不工作,無顧客等待
waitingRoom.v = 1;//是否可以進入理髮店
void Barber()
{
	while(!isGoHome()) {//理髮師是否下班
		P(waitingClient);//顧客喚醒理髮師
		P(waitingRoom);//對等待區操作,空位子增加一個
		emptySeats += 1;
		V(workingBarber);//理髮師工作
		V(waitingRoom);//撤銷對等待區操作
		//cut hair
	}
}
void Client()
{
	P(waitingRoom);//對等待區操作
	if(emptySeats > 0) {//判斷是否有空位
		emptySeats -= 1;
		V(waitingClient);//顧客在等待
		V(waitingRoom);//撤銷對等待區操作
		P(workingBarber);//理髮師爲顧客工作(同樣,P操作與上一個V操作不能交換,理由見下)
		//have haircut
	}
	else {
		V(waitingRoom);//沒有空位,離開
		//leave
	}
}
(子程序中原子操作不能交換的原因:當前操作,能保證在理髮師和其他顧客對理髮店的訪問;互換之後,可能會發生理髮師不能詢問等待區,顧客不能進入理髮店。因此,不交換更合適。)


4、

讀者---寫者問題

有一個多個進程共享的數據區,這個數據區可以是一個文件或者一塊內存空間,甚至可以使一組寄存器。有一些進程(reader)只能讀取這個數據區中的數據,一些進程(writer)只能往數據區中寫數據;此外還必須滿足一下條件:

a)任意多的讀進程可以同時讀這個文件。

b)一次只有一個寫進程可以寫文件。

c)如果一個寫進程正在寫文件,禁止任何讀進程讀文件。

1)問題分析:

對於共享的數據區,要滿足條件a),需要對讀進程與讀進程之間進行同步操作;要滿足b)和c)需要對寫進程與讀進程之間,寫進程與寫進程之間進行互斥操作。

對於讀進程,需要先判斷是否有寫進程,若有,則等待;否則,訪問數據區;

對於寫進程,需要同樣判斷是否有寫進程且讀進程,若有,則等待;否則,訪問數據區。

用標識變量isWrite標識是否寫進程運行,用numberOfReader計數器記錄有幾個讀進程運行。通過兩個互斥量write和read分別控制對兩變量的操作。

2)僞碼描述:

以下是自己寫的版本(如果有錯歡迎指正),如果希望的到可信度較高的版本,請移步此處

semaphore write, read;//用於對變量的互斥控制
bool isWrite = false;//無寫進程
int numberOfReader = 0;//無讀進程
write.v = read.v = 1;//允許對變量修改
void Writer()
{
	P(write);//申請對isWrite變量操作,如果有寫進程則等待
	P(read);//申請對numberOfReader變量操作,防止Reader對numberOfReader的改變,即避免出現寫進程之後有讀進程的情況
	if(numberOfReader == 0) {//當無讀進程時,運行寫程序;否則,釋放對numberOfReader變量的控制並離開
		isWrite = true;//標記爲寫
		//write
		isWrite = false;//將標記撤銷
		V(read);//釋放對numberOfReader變量操作
	} else {
		V(read);
	}
	V(write);//停止對isWrite變量的操作
}
void Reader()
{
	P(write);//申請對isWrite變量的操作,防止isWrite的改變,即避免出現讀進程之後有寫進程的情況
	if(!isWrite) {//判斷是否有寫進程,否則釋放對isWrite的操作並離開
		P(read);//申請對計數器的操作
		numberOfReader++;//讀進程增加一個
		V(read);//釋放操作
		V(write);//釋放對isWrite變量的操作,是其他讀進程進入,同時確保讀寫之間只有一個進程。
		//read
		P(read);
		numberOfReader--;
		V(read);//
	} else {
		V(write);
	}
}

5、

一帶閘門的運河,其上又兩架吊橋。吊橋坐落在一條公路上,爲使該公路避開一塊沼澤地而令其橫跨運河兩次。運河和公路的交通都是單方向的。運河上的基本運輸由駁船擔負。在一駁船接近吊橋A時就拉汽笛警告,若橋上無車輛,吊橋就吊起,直到駁船尾部通過此橋爲止。對吊橋B也按同樣次序處理。如何利用信號量的P、V操作,實現車輛和駁船的同步。


1)問題分析:

問題中有兩個明顯的互斥資源,吊橋A和吊橋B,因此需要兩個信號量,來表示吊橋A、B的使用;有兩個信號量的使用,需要考慮資源分配的有序性,這裏將A的優先級設置的更高些。

對駁船進行分析,

駁船先判斷吊橋A是否被使用,若無佔用,則申請;之後並判斷吊橋B的情況,無佔用,則申請。A、B有一個佔用,則等待。

當駁船申請A、B兩處資源後,則通過吊橋A後,釋放吊橋A,之後通過吊橋B,然後釋放吊橋B。

對汽車進行分析,

由於汽車會出現多輛的情況,爲了防止死鎖,需要控制車輛的數量,也就是設置一個整形變量count用來記錄車輛數量。防止多輛車輛對共享變量的更改,需要設置第三個信號量,保證對count的互斥操作。

對於某輛車,先申請資源A,如果資源A空閒,那麼申請;如果資源B空閒,那麼申請。只要A、B有一個佔用,則等待。

當車輛申請A、B兩處資源之後,則判斷當前AB段公路上是否只有這一輛車,那麼車輛通過B,釋放B,之後通過A,釋放A;如果有多輛車,則該車等待。

2)僞碼描述:

semaphore mutexA, mutexB, mutexX;//定義對A,B,count的互斥控制
int count = 0;//記錄當前車輛數量
mutexA.c = 1, mutexB.c = 1, mutexX.c = 1;//設定初始值,表示資源可用
void Car(...) {
    P(mutexX);//申請對count的使用
    count++;//車輛數加一
    if(count == 1) {//如果當前只有一輛車,則進入,申請A,B資源
        P(mutexA);
        P(mutexB);
    }
    V(mutexX);//釋放對count的使用
    /*pass B, then pass A*/
    P(mutexX);
    count--;
    if(count == 0) {//判斷當前車輛是否駛離AB公路,駛出,則釋放A,B資源
        V(mutexB);
        V(mutexA);
    }
    V(mutexX);
}
void Ship(...) {
    P(mutexA);
    P(mutexB);
    /*pass A, then pass B*/
    V(mutexA);
    V(mutexB);
} 

6、

下圖數描述交通死鎖的例子(設各方向上的汽車都是單線、直線行駛),若用計算機實現交通自動管理,請用信號量的P、V操作來實現各方向上汽車行駛的同步。

1)問題分析:

對於每一個路口,顯然是一個互斥的資源,此處就需要四個信號量來表示。兩個路口之間,需要記錄通過的車輛數,也就是需要四個記錄車輛數的計數器,爲了保證每個計數器的正常工作,則需要配有四個信號量來保證對四個計數器的同步互斥操作。

按照上北下南,左西右東的規則,對於自北向南的車輛:

需要同時申請兩個路口的通行並且只用一輛車時,纔可通過;否則都處於等待狀態。

其他三個方向的車輛也需要同樣遵守上述的規定。

對於西北的路口,記爲C1,順時針方向依次標記各個路口;自北向南的車道,記爲S1,同樣順時針方向標記各個車道。

2)僞碼描述:

semaphore C[4], S[4], usedStreet;//表示路口,車道,使用車道的信號量
C[1].v = C[2].v = C[3].v = C[4].v = 1;//表示路口可用
S[1].v = S[2].v = S[3].v = S[4].v = 1;//表示車道可用
usedStreet.v = 3;
void Car(int id)
{
	P(usedStreet);//判斷是否有剩餘可用的車道
	P(S[id]);//申請車道,如果車道空閒,則準備駛入
		P(C[id]);//申請通過C[id]路口
		P(C[(id + 1) % 4]);//申請通過C[(id + 1) % 4]路口
		//pass C[id], then pass C[(id + 1) % 4];//如果兩個路口均空閒,則通過
		V(C[id]);//通過路口C[id]
		V(C[(id + 1) % 4]);//通過路口C[(id + 1) % 4]
	V(S[id]);//通過這條車道
	V(usedStreet);//釋放對車道的使用,表示通過
}

7、

桌上有一空盤,允許存放一隻水果。爸爸可向盤中放蘋果,也可向盤中放桔子,兒子專等吃盤中的蘋果,女兒專等盤中的桔子。規定當盤中空時,一次只能放一隻水果供吃者取用,請問用信號量的P、V操作如何實現爸爸,兒子,女兒之間的同步問題。

1)問題分析:

盤子,是父子和父女之間都需要訪問的資源,需要用信號量表示;蘋果,是父子之間都處理的信息,需要用信號量表示;桔子,自然也需要用信號量來表示。

對於爸爸的行爲:

需要盤子是否佔用,

    如果沒有佔用,則判斷蘋果或桔子是否佔用(即是否分發),

        若沒有,放蘋果或桔子,否則,什麼都不做

    否則什麼都不做。

對於兒子的行爲:

判斷盤子裏是否有蘋果,

       若是,則吃掉,否則什麼都不做

對於女兒的行爲:

判斷盤子是否有桔子,

       若是,則吃掉,否則什麼都不做

2)僞碼描述:

semaphore dish, apple, orange;//定義信號量,表示盤子,蘋果,桔子三類資源。
dish.v = 1, apple.v = 0, orange.v = 0;//分別表示盤子爲空,蘋果未放,桔子未放
void father(...) {
    P(dish);//如果盤子爲空,則放水果
    V(apple);//如果蘋果未放,則放蘋果

    P(dish);//如果盤子爲空,則放水果
    V(orange);//如果桔子未放,則放桔子
}
void son(...) {
    P(apple);//如果是蘋果,則取蘋果
    V(dish);//將盤子設爲空
}
void daughter(...) {
    P(orange);//如果是桔子,則取桔子
    V(dish);//將盤子設爲空
}

小結:

(我的一些淺顯的見解,大神如果有好的想法,求分享呀)

解決實際問題

首先,需要對信號量操作有夠深的理解,

1)信號量的賦值是爲了,描述資源的使用情況;

2)對信號量的P()操作,其意義是:減一操作後,信號量的值爲正,則執行該進程;否則阻塞;

3)對信號量的V()操作,其意義是:增一操作後,信號量的值爲小於等於零,則從阻塞隊列中取出阻塞進程並執行;否則什麼也不做。

其次,我們需要對問題本身,進行分析。該過程中需要完成確定:

1)互斥量(通過二元信號量解決,還是計數信號量解決,或者兩者共用的方式解決),

2)進程與進程之間的關係(同步還是互斥,比如生產者、消費者之間的關係),

3)確定資源的申請順序(防止出現死鎖,飢餓現象,比如哲學家進餐)。

等等

最後,對照實際問題對信號量賦予實際的含義,並通過P(),V()操作模擬問題本身。


發佈了48 篇原創文章 · 獲贊 10 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章