1. 程式人生 > >程序互斥和程序同步

程序互斥和程序同步

一些概念:
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 = 01 
{ 
  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鏈為空,則相當於空操作,執行此操作的程序繼續執行;否則喚醒第一個等待者,執行此操作的程序進入緊急等待佇列的末尾。