1. 程式人生 > >訊號量:整型、記錄型訊號量以及利用訊號量實現程序互斥和前驅關係

訊號量:整型、記錄型訊號量以及利用訊號量實現程序互斥和前驅關係

訊號量機構是一種功能較強的機制,可用來解決互斥與同步的問題,它只能被兩個標準的原語wait(S)和signal(S)來訪問,也可以記為“P操作”和“V操作”。

原語是指完成某種功能且不被分割不被中斷執行的操作序列,通常可由硬體來實現完成不被分割執行特性的功能。如前述的“Test-and-Set”和“Swap”指令,就是由硬體實現的原子操作。原語功能的不被中斷執行特性在單處理機時可由軟體通過遮蔽中斷方法實現。

原語之所以不能被中斷執行,是因為原語對變數的操作過程如果被打斷,可能會去執行另一個對同一變數的操作過程,從而出現臨界段問題。如果能夠找到一種解決臨界段問題的元方法,就可以實現對共享變數操作的原子性。

整型訊號量

整型訊號量被定義為一個用於表示資源數目的整型量S,wait和signal操作可描述為:
  1. wait(S){
  2. while (S<=0);
  3. S=S-1;
  4. }
  5. signal(S){
  6. S=S+1;
  7. }
wait操作中,只要訊號量S<=0,就會不斷地測試。因此,該機制並未遵循“讓權等待” 的準則,而是使程序處於“忙等”的狀態。

記錄型訊號量

記錄型訊號量是不存在“忙等”現象的程序同步機制。除了需要一個用於代表資源數目的整型變數value外,再增加一個程序連結串列L,用於連結所有等待該資源的程序,記錄型訊號量是由於釆用了記錄型的資料結構得名。記錄型訊號量可描述為:
  1. typedef struct{
  2. int value;
  3. structprocess *L;
  4. } semaphore;
相應的wait(S)和signal(S)的操作如下:
  1. void wait (semaphoreS) { //相當於申請資源
  2. S.value--;
  3. if(S.value<0) {
  4. add this process toS.L;
  5. block(S.L);
  6. }
  7. }
wait操作,S.value--,表示程序請求一個該類資源,當S.value<0時,表示該類資源已分配完畢,因此程序應呼叫block原語,進行自我阻塞,放棄處理機,並插入到該類資源的等待佇列S.L中,可見該機制遵循了“讓權等待”的準則。
  1. void signal (semaphoreS) { //相當於釋放資源
  2. S.value++;
  3. if(S.value<=0){
  4. remove a process P fromS.L;
  5. wakeup(P);
  6. }
  7. }
signal操作,表示程序釋放一個資源,使系統中可供分配的該類資源數增1,故S.value++。若加1後仍是S.value<=0,則表示在S.L中仍有等待該資源的程序被阻塞,故還應呼叫wakeup 原語,將S.L中的第一個等待程序喚醒。

利用訊號量實現同步

訊號量機構能用於解決程序間各種同步問題。設S為實現程序P1、P2同步的公共訊號量,初值為0。程序P2中的語句y要使用程序P1中語句x的執行結果,所以只有當語句x執行完成之後語句y才可以執行。其實現程序同步的演算法如下:
  1. semaphoreS = 0; //初始化訊號量
  2. P1 ( ) {
  3. // …
  4. x; //語句x
  5. V(S); //告訴程序P2,語句乂已經完成
  6. }
  7. P2(){
  8. // …
  9. P(S) ; //檢查語句x是否執行完成
  10. y; // 檢查無誤,執行y語句
  11. // …
  12. }

利用訊號量實現程序互斥

訊號量機構也能很方便地解決程序互斥問題。設S為實現程序Pl、P2互斥的訊號量,由於每次只允許一個程序進入臨界區,所以S的初值應為1(即可用資源數為1)。只需把臨界區置於P(S)和V(S)之間,即可實現兩程序對臨界資源的互斥訪問。其演算法如下:
  1. semaphoreS = 1; //初化訊號量
  2. P1 ( ) {
  3. // …
  4. P(S); // 準備開始訪問臨界資源,加鎖
  5. // 程序P1的臨界區
  6. V(S); // 訪問結束,解鎖
  7. // …
  8. }
  9. P2() {
  10. // …
  11. P(S); //準備開始訪問臨界資源,加鎖
  12. // 程序P2的臨界區;
  13. V(S); // 訪問結束,解鎖
  14. // …
  15. }
互斥的實現是不同程序對同一訊號量進行P、V操作,一個程序在成功地對訊號量執行了 P操作後進入臨界區,並在退出臨界區後,由該程序本身對該訊號量執行V操作,表示當前沒有程序進入臨界區,可以讓其他程序進入。

利用訊號量實現前驅關係

訊號量也可以用來描述程式之間或者語句之間的前驅關係。圖2-8給出了一個前驅圖,其中S1, S2, S3, …, S6是最簡單的程式段(只有一條語句)。為使各程式段能正確執行,應設定若干個初始值為“0”的訊號量。例如,為保證S1 -> S2、 S1 -> S3的前驅關係,應分別設定訊號量a1、a2。同樣,為了保證 S2 -> S4、S2 ->S5、S3 -> S6、S4 -> S6、S5 -> S6,應設定訊號量bl、b2、c、d、e。 


圖2-8 前驅圖舉例
實現演算法如下:
  1. semaphoreal=a2=bl=b2=c=d=e=0; //初始化訊號量
  2. S1() {
  3. // …
  4. V(al); V(a2) ; //S1已經執行完成
  5. }
  6. S2() {
  7. P(a1); //檢查S1是否執行完成
  8. // …
  9. V(bl); V(b2); // S2已經執行完成
  10. }
  11. S3() {
  12. P(a2); //檢查S1是否已經執行完成
  13. // …
  14. V(c); //S3已經執行完成
  15. }
  16. S4() {
  17. P(b1); //檢查S2是否已經執行完成
  18. // …
  19. V(d); //S4已經執行完成
  20. }
  21. S5() {
  22. P(b2); //檢查S2是否已經執行完成
  23. // …
  24. V(e); // S5已經執行完成
  25. }
  26. S6() {
  27. P(c); //檢查S3是否已經執行完成
  28. P(d); //檢查S4是否已經執行完成
  29. P(e); //檢查S5是否已經執行完成
  30. // …;
  31. }

分析程序同步和互斥問題的方法步驟

1) 關係分析。找出問題中的程序數,並且分析它們之間的同步和互斥關係。同步、互斥、前驅關係直接按照上面例子中的經典範式改寫。

2) 整理思路。找出解決問題的關鍵點,並且根據做過的題目找出解決的思路。根據程序的操作流程確定P操作、V操作的大致順序。

3) 設定訊號量。根據上面兩步,設定需要的訊號量,確定初值,完善整理。