程序互斥和程序同步
一些概念:
1.臨界資源(critical resource):
系統中某些資源一次只允許一個程序使用,稱這樣的資源為臨界資源(或互斥資源)。
2.臨界區(互斥區)(critical section(region)):各個程序中對某個臨界資源(互斥資源)實施操作的程式片段。
3.程序互斥(mutual exclusive):由於各程序要求使用共享資源(變數、檔案等),而這些資源需要排他性使用,因此各程序之間競爭使用這些資源,這一關係稱為程序互斥。
4.程序同步(synchronization):指系統中多個程序中發生的事件存在某種時序關係,需要相互合作,共同完成一項任務。具體地說,一個程序執行到某一點時,要求另一夥伴程序為它提供訊息,在未獲得訊息之前,該程序進入阻塞態,獲得訊息後被喚醒進入就緒態。
軟、硬體方法解決程序互斥問題:
1.軟體解法:
(1).Dekker演算法:
//程序P:
//...
pturn = true;
{
if (turn == 2)
{
pturn = false;
while (turn == 2);
pturn = true;
}
}
/*...
臨界區
...*/
turn = 2;
pturn = false;
//...
//程序Q:
//...
qturn = true;
while (pturn)
{
if (turn == 1)
{
qturn = false;
while (turn == 1 );
qturn = true;
}
}
/*...
臨界區
...*/
turn = 1;
qturn = false;
//...
(2).Peterson演算法:
#define FALSE 0
#define TRUE 1
#define N 2 // 程序的個數
int turn; // 輪到誰?
int interested[N];// 興趣陣列,初始值均為FALSE
void enter_region ( int process)// process = 0 或 1
{
int other;// 另外一個程序的程序號
other = 1 - process;
interested[process] = TRUE ;// 表明本程序感興趣
turn = process;// 設定標誌位
while( turn == process && interested[other] == TRUE); //迴圈
}
void leave_region ( int process)
{
interested[process] = FALSE;// 本程序已離開臨界區
}
//程序i:
//...
enter_region ( i );
/*...
臨界區
...*/
leave_region ( i );
//...
(雖然自選鎖在一定程度上會白白浪費CPU時間片,但是在多CPU的環境中,對持有鎖較短的程式來說,使用自旋鎖代替一般的互斥鎖往往能夠提高程式的效能。)
2.硬體解法:
有中斷遮蔽方法、“測試並加鎖”指令、“交換指令”等方法。
同步機制其一:訊號量及P、V操作:
(1).訊號量:一個整數值,用於程序間傳遞資訊。
struc semaphore
{
int count;
queueType queue;
}
對訊號量可以施加的操作只有三種:初始化、P和V。
(2).P、V操作:均為原語操作
semaphore s = 1;
//一種實現方式
P(s) //P操作,又叫down或semWait操作
{
s.count --;
if (s.count < 0)
{
/*
該程序狀態置為阻塞狀態;
將該程序插入相應的等待佇列s.queue末尾;
重新排程;
*/
}
}
V(s)//V操作,又叫up或semSignal操作
{
s.count ++;
if (s.count < = 0)
{
/*
喚醒相應等待佇列s.queue中等待的一個程序;
改變其狀態為就緒態,並將其插入就緒佇列;
*/
}
}
最初提出的是二元訊號量(解決互斥),之後推廣到一般訊號量(多值)或計數訊號量(解決同步)。
用訊號量解決問題:
1.生產者——消費者問題:
#define N 100 //緩衝區個數
typedef int semaphore; //訊號量是一種特殊的整型資料
semaphore mutex =1; //互斥訊號量:控制對臨界區的訪問
semaphore empty =N; //空緩衝區個數
semaphore full = 0; //滿緩衝區個數
void producer(void) //生產者程序
{
int item;
while(TRUE)
{
item=produce_item();
P(&empty);
P(&mutex);
insert_item(item); //臨界區
V(&mutex)
V(&full);
}
}
void consumer(void) //消費者程序
{
int item;
while(TRUE)
{
P(&full);
P(&mutex);
item=remove_item();//臨界區
V(&mutex);
V(&empty);
consume_item(item);
}
}
//兩個P操作的順序不能顛倒,會引起死鎖,
//V操作的順序可以顛倒,但是會引起臨界區擴大等問題。
2.讀者——寫者問題:
問題概述:多個程序共享一個數據區,這些程序分為兩組:讀者程序——只讀資料區中的資料,寫者程序——只往資料區寫資料。允許多個讀者同時執行讀操作;不允許多個寫者同時操作;不允許讀者、寫者同時操作。
第一類——讀者優先:
讀者執行:當無其他讀、寫者時;
當有其他讀者在讀時;
寫者執行:當無其他讀、寫者時;
typedef int semaphore;
int rc = 0; //reader counter,共享變數
semaphore rw_mutex = 1;//讀寫臨界區保護鎖
semaphore rc_mutex = 1;//有多個讀者時,rc是我們人為引進的一個
//臨界區資源,也需要提供鎖保護
//讀者程序:
void reader(void)
{
while (TRUE)
{
P(rc_mutex);//對rc上鎖
rc = rc + 1;
if (rc == 1) //如果是第一個讀者
P(rw_mutex);//對讀寫臨界區上鎖
V(rc_mutex);//對rc操作完畢,解鎖
read();//讀寫臨界區,讀操作
P(rc_mutex);
rc = rc - 1;
if (rc == 0) //如果是最後一個讀者
V(rw_mutex);//釋放讀寫臨界區
V(rc_mutex);
}
void writer(void)
{
while (TRUE)
{
P(rw_mutex);
write();
V(rw_mutex);
}
}
另外兩類——寫者優先、公平競爭:https://blog.csdn.net/cz_hyf/article/details/4443551
(一個應用:Linux的IPX路由程式碼中使用了讀-寫鎖,用ipx_routes_lock的讀-寫鎖保護IPX路由表的併發訪問)
同步機制其二:管程機制:
1.管程:由關於共享資源的資料結構及在其上操作的一組過程組成(程序只能通過呼叫管程中的過程來間接地訪問管程中的資料結構),是一種高階同步機制。
2.管程兩個重要特性:
(1)管程是互斥進入的:為了保證管程中資料結構的資料完整性,管程的互斥性是由編譯器負責保證的。
(2)管程中設定條件變數及等待/喚醒操作(wait/signal):可以讓一個程序或執行緒在條件變數上等待(此時,應先釋放管程的使用權),也可以通過傳送訊號將等待在條件變數上的程序或執行緒喚醒
3.分類:
P進入管程,執行等待操作並釋放管程互斥權,此時Q進入管程,喚醒P程序,管程中就有了兩個活動程序,根據對這種情況的處理,分為:
(1)Hoare管程:Q(喚醒者)等待,P(被喚醒者)執行;
(2)MESA管程:P等待Q繼續執行;
(3)Hansen管程:規定喚醒操作為管程中最後一個可執行操作。
4.Hoare管程簡介:
(1)因為管程是互斥進入的,所以當一個程序試圖進入一個已被佔用的管程時,應當在管程的入口處等待,為此,管程的入口處設定一個程序等待佇列,稱作入口等待佇列。
(2)如果程序P喚醒程序Q,則P等待Q執行;如果程序Q執行中又喚醒程序R,則Q等待R執行;……,如此,在管程內部可能會出現多個等待程序,在管程內需要設定一個程序等待佇列,稱為緊急等待佇列,緊急等待佇列的優先順序高於入口等待佇列的優先順序。
(3)條件變數——在管程內部說明和使用的一種特殊型別的變數(定義一個條件變數c,var c:condition;)。
對於條件變數,可以執行wait和signal操作:
- wait(c): 如果緊急等待佇列非空,則喚醒第一個等待者;否則釋放管程的互斥權,執行此操作的程序進入c鏈末尾。
- signal(c): 如果c鏈為空,則相當於空操作,執行此操作的程序繼續執行;否則喚醒第一個等待者,執行此操作的程序進入緊急等待佇列的末尾。