進程同步概念簡介 多線程上篇(四)

進程同步概念

臨界資源

一旦有對資源的共享,就必然涉及競爭限制
好比儘管有兩我的去水井打水,可是水井卻只有一個;合理安排的話恰好錯開,可是若是安排不合理,那就會出現衝突,出現衝突怎麼辦?總有一個先來後到,等下就行了。
這個水井就是一個臨界資源
臨界資源用來表示一種公共資源或者說是共享數據,能夠被多個線程使用。
可是每一次,只能有一個線程使用它,一旦臨界資源被佔用,其餘線程要想使用這個資源,就必須等待。
當多進程訪問臨界資源時,好比打印機
假設A進程和B進程輪流得到CPU時間片執行,A打印數學,B打印英語,若是不進行任何的控制與限制,可能會打印出來一張紙上一道數學計算題,接下來是一段英語的狀況。
因此儘管A進程和B進程輪流得到時間片運行,可是當須要訪問臨界資源時,一旦有一個進程已經開始使用,另外的進程就不能進行使用,只能等待。
 
計算機就是那個計算機,硬盤就是那個硬盤,一段代碼中的某個變量(共享變量)就是那個變量.....全部的一切都是隻有一份,若是對於某個點多進程同時訪問,必然要作必定的限制
進程同步的主要任務是對多個相關進程在執行次序上進行協調,以使併發執行的諸進程之間能有效地共享資源和相互合做,從而使程序的執行具備可再現性

兩種制約關係

既然資源訪問有限制,到底有哪些場景是須要同步處理的?也就是什麼時候會出現資源衝突?
image_5c567cee_2c7a
看得出來,其實同步要解決的問題根本就是競爭,間接關係是赤裸裸的的競爭,共享同一個I/O就是一種競爭,儘管他們看似好像沒有什麼關係
直接的制約關係,源於進程間的合做,某種程度上來講也是一種競爭,只不過是有條件的競爭,他們共享緩衝區,當緩衝區滿時只能是消費者能夠運行,生產者須要阻塞,這能夠認爲緩衝區滿這種狀況下,消費者獨佔了緩衝區,生產者不能使用了,不過這種狀況下仍是說成合做比較容易理解
因此,要麼是由於共享資源帶來的競爭,要麼就是相互合做帶來的依賴。

臨界區

有了臨界資源的概念,就很容易理解臨界區的概念,在程序中,全部的操做都是經過代碼執行的,訪問臨界資源的那段代碼就是臨界區
以打水爲例,因此在還沒到井口,就要畫一個大圈,不容許第二我的進入範圍,「請站在安全黃線內」這句話熟悉麼?
這就是臨界區。
image_5c567cef_528a

同步規則

如何纔可以合理處理競爭或者合做依賴致使的制約?
  • 空閒讓進
  • 忙則等待
  • 有限等待
  • 讓權等待
空閒讓進和忙則等待很好理解,對於臨界資源,若是空閒沒有被使用,誰來了以後均可以使用;若是臨界資源正在被使用,那麼其餘後來者就須要進行等待
有限等待是指,要求訪問臨界資源的進程,應保證有限時間內能進入本身的臨界區,本身不能傻傻的等,傻傻等受傷的是本身
讓權等待是指,若是沒法進入本身的臨界區時,應當即釋放處理機,而不能佔着CPU死等,你死等就算了,別人卻也不能用了
有限等待和讓權等待是兩個維度
你不能爲了一件事情不顧一切代價等個天荒地老,太傷身了;
若是你非要花五塊錢去蘇寧買一臺電視(等待事件發生),人家不賣給你(沒法進入臨界區),你就賴着不走(忙等),你就耽誤別人作生意了(別的進程沒法得到CPU)
有限等待和讓權等待的共同特性是必須保證有條件的退出以給其餘進程提供運行的機會
簡單說就是有限時間內你就要走開,你得不到更要走開,你即便能獲得可是時間過久也得先讓一下別人
image_5c567cef_6f63
 
臨界區的設置就是安全黃線的設置,同步規則其實就是臨界區兩條黃線進出規則
對於臨界區,還能夠進一步細分出來進入區和退出區以及剩餘區
image_5c567cef_1667

臨界區算法

Peterson算法
因此臨界區方式解決同步問題就是藉助於算法,合理的控制對於臨界區的進入與退出,而且保障可以作到:空閒讓進、忙則等待、有限等待、讓權等待
一種有名的算法爲 Peterson
Peterson算法適用於兩個進程在臨界區與剩餘區間交替執行。假設兩個進程分別爲p0 和 p1
爲了表示方便,使用pi表示其中一個進程時,pj表示另一個,顯然有i = 1-j(j = 1-i)
image_5c567cef_689f
使用一個int類型變量turn 表示能夠進入臨界區的線程,若是turn == i,表示pi能夠進入臨界區
使用boolean 類型數組flag,總共兩個進程,因此flag[2],用來表示哪一個進程想要進入臨界區,若是flag[i] = true;表示進程pi想要進入臨界區
上圖紅框內爲進入區,藍框內爲退出區
 
爲了進入臨界區,進程pi首先設置flag[i]爲true;而且設置turn爲j;
顯然,根據while的條件,只有flag[j] == false 或者turn == i 時,pi能夠進入臨界區
也就是若是我想進入的話,當對方不想進入或者當前容許我進入時,我就能夠進入臨界區
顯然,若是隻有一個進程想要進入,那麼如上所述,對方不想進入時,能夠進入臨界區,符合空閒讓進
 
若是兩個進程都想進入,無論通過多麼激烈的競爭,當執行到while時flag[0] 和 flag[1] 都是true,也就是while內部的兩個條件,條件1始終是true
可是turn只會有一個值,要麼0 要麼1,也就是說while的第2個條件決定了誰會被while阻塞,誰可以繼續執行下去
這種狀況下必然可以有一個進程會進入臨界區,另一個被while循環阻塞,因此符合空閒讓進、忙則等待
 
當臨界區內的進程執行結束後,會設置flag[] 標誌位,若是此時另外的進程在等待,一旦設置後,其餘進程就能夠進入臨界區(剛纔已經說了,若是pi想進入,flag[j] == false 或者turn == i 時能夠進入)也就是說當前進程結束後,下一個進程就可以進入了,因此知足有限等待
 
小結:
上面的算法,知足了經過進入區和退出區代碼的設置,能夠作到同步的規則
若是隻有一個想要進入臨界區,能夠直接進入,若是兩個競爭進入,只有一個可以進入,另外一個會被while循環阻塞
Peterson只是一種臨界區算法,還有其餘的

同步方式之信號量

1965年,荷蘭學者Dijkstra 提出的信號量(Semaphores)機制是一種卓有成效的進程同步工具。
臨界區算法的原理可讓多進程對於臨界區資源的訪問串行化;
信號量機制容許多個線程在同一時刻訪問同一資源,可是須要限制在同一時刻訪問此資源的最大線程數目。

整型信號量

最初信號量機制被稱之爲整型信號量
最初由Dijkstra 把整型信號量定義爲一個用於表示資源數目的整型量 S,它與通常整型量不一樣,除初始化外,僅能經過兩個標準的原子操做(Atomic  Operation)  wait(S)和 signal(S)來訪問。
這兩個操做一直被分別稱爲P、V操做(聽說,聽說由於Dijkstra是荷蘭人。在最初論文以及大多數文獻中,用P表明wait。用V表明signal。是荷蘭語中高度 proberen 和增量verhogen)
Wait(S)和 signal(S)操做可描述爲:
   wait(S):  while (S<=0); 
                  S:=S-1;
  signal(S):S:=S+1; 
 
wait表示資源申請:若是S小於等於0(資源不足)等待,若是知足那麼將會進行S-1,也就是申請資源
signal表示資源釋放:每釋放一次資源,資源數目 S+1
P、V操做也稱之爲操做原語,就是指原子操做
原子性 是指一個操做是不可中斷的,要麼所有執行成功要麼所有執行失敗(具體怎麼保證的,此處能夠不用關注) 
image_5c567cef_1c60
多個線程併發的對資源進行訪問時,藉助於PV原語操做,能夠有效地作到共享資源的限制訪問。
可是,對於整型信號量,P操做也就是 wait(S)
wait(S):  while (S<=0);
                 S:=S-1;
 
若是獲取不到資源,將會持續while (S<=0);,將會永遠等待,進程處於忙等狀態
關於原語
wait(S)和 signal(S)這一原子操做叫作原語,原語是操做系統概念的術語,是由若干條指令組成的,用於完成必定功能的一個過程
是由若干個機器指令構成的完成某種特定功能的一段程序,具備不可分割性,即原語的執行必須是連續的,在執行過程當中不容許被中斷。
原語是操做系統的核心部分組成,原語有不可中斷性。它必須在管態(內核態,系統態)下執行,而且常駐內存,而個別系統有一部分不在管態下運行。
能夠簡單的理解是具備指定功能的操做系統提供的一個API性質的一種東西

記錄型信號量

鑑於整型信號量機制中的「忙等」狀況,演化出來記錄型信號量
若是進程沒法進入臨界區,那麼進入等待釋放CPU資源,而且經過一個鏈表記錄等待的進程。
記錄型信號量機制在整形信號量機制的基礎上增長了進程鏈表指針L,這也是記錄型信號量名稱的由來
記錄型信號量 semaphore 的結構以下:
semaphore {
value:int value;
L:進程等待鏈表(集合);
}
value至關於整型信號量中的S,L就是一個鏈表(集合)
簡言之,將整形信號量中的整型S,演化爲一個結構,這個結構包括一個整型值,還有一個等待的進程鏈表
image_5c567cef_4151
相對應整型信號量中的wait(S) 和 signal(S)能夠描述爲:
wait(S):
var S = semaphore;
S.value=S.value-1if S.value<0 then block(S.L); 

signal(S):
var S = semaphore;
S.value=S.value+1if S.value<=0 then wakeup(S.L);  
上面的操做中,均定義了一個semaphore類型的變量S
  • 若是執行 wait 操做,先執行資源減一,若是此時S.value<0,說明在申請資源以前(S.value-1),原來的資源就是<=0,那麼該進程阻塞,加入等待隊列L中
  • 若是執行 signal 操做,先執行資源加一,若是此時S.value<=0,說明在釋放資源以前(),原來的資源是<0的,那麼將等待鏈表中的進程喚醒
上面邏輯的關鍵之處在於:
  • 當申請資源時,先進行S.value-1,一旦資源出現負數,說明須要等待,S.value的絕對值就是等待進程的個數,也就是S.L的長度
  • 當資源恢復時,先進行S.value+1,已經有人釋放資源瞭然而資源個數仍是小於等於0,說明原來就有人在等待,因此應該去喚醒
block 和 wakeup 也都是原語,也就是原子操做。
block原語,進行自我阻塞,放棄處理機,並插入到信號量鏈表S.L 中
wakeup原語,將S.L鏈表中的等待進程喚醒
 
若是 S.value的初值爲 1,表示只容許一個進程訪問臨界資源,此時的信號量轉化爲互斥信號量,用於進程互斥。(效果就如同Peterson算法了)

AND 型信號量

針對於臨界區算法或者是整型信號量或者是記錄型信號量是針對各進程之間只共享一個臨界資源而言的。
可是有些場景下,一個進程須要先得到兩個或更多的共享資源後方能執行其任務。
假設A,B兩個進程,均須要申請資源D,E
process A:     process B:
wait(D);      wait(E);
wait(E);       wait(D); 
假設交替執行順序以下
process A: wait(D); 因而D=0
process B: wait(E); 因而E=0
process A: wait(E); 因而E=-1 A阻塞
process B: wait(D); 因而D=-1 B阻塞 
最終A,B都被阻塞,若是沒有外力做用下,二者都沒法從阻塞狀態釋放出來,這就是死鎖

相關概念

對於一個水井,你在打水,另外的人就要等一等,這是人類大腦意識作出來的很基本的反應(人眼識別,大腦解析而且作出反應)
可是計算機程序並無這麼智能,你須要對他進行某些處理,以限制別的線程的訪問,好比你能夠將「安全黃線」變成一個安全門,好比廁所,進去了以後把門關上。。。
這種概念就是鎖,鎖就是對資源施加控制,鎖指的是一種控制權
當進入臨界區時,咱們稱之爲得到鎖,得到鎖以後就能夠訪問臨界資源;
其餘線程想要進入臨界區,也須要先得到鎖,顯然,他們獲取不到,由於此時,鎖被當前正在執行的線程持有
當前線程結束後,將會釋放鎖,別得線程就能夠獲取這個資源的鎖,而後....
死鎖
鎖表示一種控制權,對臨界資源的訪問權限,若是臨界資源不止一個,就可能出現這種狀況:
須要前後訪問兩種臨界資源A和B,thread1得到了A線程的鎖以後,等待得到B的鎖,可是thread2得到了資源B的鎖,在等待A資源的鎖,這就出現了互相等待的狀況
好比一條窄橋,同一時刻僅僅容許一輛車經過,若是一旦出現兩輛車都走到橋的一半處,並且各執己見,怎麼辦?這就是死鎖 
解決方案
AND型信號量機制就是用於解決這種多共享資源下的同步問題的
AND 同步機制的基本思想:
將進程在整個運行過程當中須要的全部資源,一次性所有地分配給進程,待進程使用完後再一塊兒釋放。
只要尚有一個資源未能分配給進程,其它全部可能爲之分配的資源也不分配給它。
也就是對若干個臨界資源的分配,採起原子操做方式:要麼把它所請求的資源所有分配到進程,要麼一個也不分配。
這種思惟就是經過對「若干個臨界資源「的原子分配,邏輯上就至關於一份共享資源,要麼獲得,也麼得不到,因此就不會出現死鎖
在 wait 操做中,增長了一個「AND」條件,因此被稱爲AND 同步,也被稱爲同時wait操做,即Swait(Simultaneous wait),相應的signal被稱爲Ssignal(Simultaneous signal)
image_5c567cef_1391
Swait(S) 和 Ssignal(S)能夠描述爲:
Swait(S1,S2,…,Sn)
        if(Si>=1 and …  and Sn>=1){
          for( i=1 to n){
          Si=Si-1;
          }
        }else{
        將進程插入到第一個資源<1 的對應的S的隊列中,而且程序計數器設置到Swait的開始(好比S1 S2 都大於等於1,可是 S3<1,那麼就插入到S3.L中;從頭再來就是爲了總體分配)
        }

  Ssignal(S1,S2,…,Sn)
          for(i=1 to n){
          Si=Si+1;
          將與Si關聯的全部進程從等待隊列中刪除,移入到就緒隊列中
          }
AND型信號量機制藉助於對於多個資源的「批量處理」的原子操做方式,將多資源的同步問題,轉換爲一個「同一批資源」的同步問題

信號量集

記錄型信號量機制中,只是對信號量加1(S+1 或者S.value+1) 或者 減1(S-1 或者S.value-1)操做,也就是隻能得到或者釋放一個單位的臨界資源
若是想要一次申請N個呢?
若是進行N次的資源申請怎麼辦,一種方式是可使用屢次Wait(S)操做,可是顯然效率較低;
另外有時候當資源數量低於某一下限值時,就不進行分配怎麼辦?須要在每次分配資源前檢查資源的數量。
爲了解決這兩個問題,對AND信號量機制進一步擴展,造成了通常化的「信號量集」機制。
 
Swait操做可描述以下
其中S爲信號量,d爲需求值,而 t爲下限值。
Swait(S1,t1,d1,…,Sn,tn,dn)
  if( Si>=t1 and …  and Sn>=tn){
          for(i=1 to n){
                  Si=Si-di;
          }
  }else{
          將進程插入到第一個資源Si<ti 的對應的S的隊列中,而且程序計數器設置到Swait的開始(好比S1 S2 都大於等於t1,t2,可是 S3<t3,那麼就插入到S3.L中;從頭再來就是爲了總體分配)    
  }

Ssignal(S1,d1,…,Sn,dn)
         for(i=1 to n){
                  Si=Si+di;
                將與Si關聯的全部進程從等待隊列中刪除,移入到就緒隊列中
          }
 
信號量集就是AND型信號量機制將資源限制擴展到Ti
  • Swait(S,d,d)。此時在信號量集中只有一個信號量 S,但容許它每次申請 d 個資源,當現有資源數少於d時,不予分配。
  • Swait(S,1,1)。此時的信號量集已蛻化爲通常的記錄型信號量(S>1時)或互斥信號量(S=1 時)。
  • Swait(S,1,0)。這是一種很特殊且頗有用的信號量操做。當 S≥1 時,容許多個進程進入某特定區;當 S 變爲 0 後,將阻止任何進程進入特定區。換言之,它至關於一個可控開關。
上面的格式爲(s,t,d)也就是第一個爲信號量,第二個爲限制,第三個爲需求量
因此Swait(S,1,0)能夠用作開關,只要S>=1,>=1時可分配,每次分配0個,因此只要S>=1,永遠都進的來,一旦S<1,也就是0日後,那麼就不知足條件,就一個都進不去
 

小結

臨界區機制經過算法控制進程串行進入臨界區,而信號量機制則是藉助於原語操做(原子性)對臨界資源進行訪問控制
按照各類信號量機制對應的規則以及相應的原語操做,就能夠作到對資源的共享同步訪問,而不會出現問題
信號量機制總共有四種
image_5c567cef_148c
整型信號量機制能夠處理同一共享資源中,資源數目不止一個的狀況
記錄型信號量對整型信號量機制的「忙等」進行了優化,經過block以及weakup原語進行阻塞和通知
AND型信號量機制解決了對於多個共享資源的同步
信號量集是對AND的再一次優化,既可以處理多個共享資源同步的問題,還可以設置資源申請的下限,是一種更加通用的處理方式

信號量的應用

實現資源互斥訪問

爲使多個進程能互斥地訪問某臨界資源,只須爲該資源設置一互斥信號量 mutex,並設其初始值爲1
而後將各進程訪問該資源的臨界區 CS置於 wait(mutex)和 signal(mutex)操做之間便可。
這樣,每一個欲訪問該臨界資源的進程在進入臨界區以前,都要先對 mutex 執行wait操做,若該資源此刻未被訪問,本次wait操做必然成功,進程即可進入本身的臨界區,不然進程將會阻塞。
步驟:
.......
wait(mutex);
臨界區
signal(mutex);
剩餘區                                          
.......

實現前趨關係

前驅關係就是指執行順序,好比要求語句S1執行結束以後才能執行語句S2
在進程P1中:
S1;
signal(S);
在進程P2中
wait(S);
S2; 
顯然,初始時將資源S設置爲0,S2須要獲取到資源纔會執行,而S1執行後就會釋放資源
對於一個更加複雜的前驅關係圖,如何實現?
image_5c567cef_399c
從圖中能夠看得出來,有S2和S3依賴S1
S4 和 S5依賴S2,而S6依賴S三、S四、S5
因此,S1應該提供兩個信號量,提供給S2和S3 使用
S2 應該等待S1的信號量,而且提供兩個信號量給S4 和 S5 
S3 應該等待S1的信號量,而且提供一個信號量給S6
S4應該等待S2的信號量,而且提供一個給S6
S5應該等待S2的信號量,而且提供一個給S6
S6應該等待S三、S四、S5
因此總共須要2+2+1+1+1 6個信號量,咱們取名爲a,b,c,d,e,f,g
image_5c567cef_64d4
那麼過程以下:
Var a,b,c,d,e,f,g:semaphore: =0,0,0,0,0,0,0;
P1{
        S1;
        signal(a);
        signal(b);
}
P2{
        wait(a);
        S2;
        signal(c);
        signal(d);
}
P3{
        wait(b);
        S3;
        signal(e);
}
P4{
        wait(c);
        S4;
        signal(f);
}
P5{
        wait(d);
        S5; 
        signal(g);
}
P6{
        wait(e);
        wait(f); 
        wait(g);
        S6;
}
有人可能會疑惑,這裏的wait和signal又是什麼?他就是信號量機制中的 wait 和 signal,他的內容是至關於下面的這些
image_5c567cef_6edf

同步方式之管程

雖然信號量機制是一種既方便、又有效的進程同步機制但每一個要訪問臨界資源的進程都必須自備同步操做 wait(S)和 signal(S),這就使大量的同步操做分散在各個進程中。
這不只 給系統的管理帶來了麻煩,並且還會因同步操做的使用不當而致使系統死鎖
在解決上述問題的過程當中,便產生了一種新的進程同步工具——管程(Monitors)。
因此管程也能夠這麼理解:它可以確保臨界資源的同步訪問,而且還不用將大量的同步操做分散到各個進程中。

管程的定義

系統中的各類硬件資源和軟件資源,都可用數據結構抽象地描述其資源特性
好比一個IO設備,有狀態(空閒仍是忙時?),以及能夠對他採起的操做(讀取仍是寫入?)以及等待該資源的進程隊列來描述,因此就能夠從這三個維度抽象描述一個IO設備,而不關注他們的內部細節
image_5c567cef_5971
又好比,一個集合,可使用集合大小,類型,以及一組可執行操做來描述,好比Java中的ArrayList,有屬性,還有方法
image_5c567cef_4b29
 
能夠把對該共享數據結構實施的操做定義爲一組過程
好比資源的請求和釋放過程定義爲request 和 release
進程對共享資源的申請、釋放和其它操做,都是經過這組過程對共享數據結構的操做來實現的
類比到JavaBean的話,這些操做就如同setter和getter方法,全部對於指定對象的操做訪問都須要經過getter和setter方法 ,相似,全部對共享數據結構實施的操做,都須要藉助於這一組過程。
image_5c567cef_216b
 
這組過程還能夠根據資源的狀況,或接受或阻塞進程的訪問,確保每次僅有一個進程使用共享資源,這樣就能夠統一管理對共享資源的全部訪問,實現進程互斥
仍是類比到JavaBean,就是至關於又增長了幾個方法,這些方法提供了更多的邏輯判斷控制
 
表明共享資源的數據結構,以及由對該共享數據結構實施操做的一組過程所組成的資源管理程序,共同構成了一個操做系統的資源管理模塊,咱們稱之爲管程。
Dijkstra於1971年提出:
把全部進程對某一種臨界資源的同步操做都集中起來,構成一個所謂的祕書進程。
凡要訪問該臨界資源的進程,都需先報告祕書,由祕書來實現諸進程對同一臨界資源的互斥使用。
管程的概念經由Dijkstra提出的概念演化而來,由Hoare和Hanson於1973年提出。
定義以下: 
一組相關的數據結構和過程一併稱爲管程。  
Hansan的定義:一個管程定義了一個數據結構和能爲併發進程在該數據結構上所執行的一組操做,這組操做能同步進程和改變管程中的數據。
因此管程的核心部分是對共享數據抽象描述的 數據結構 以及能夠對該數據結構實施操做的一組 過程
使用數據結構對共享資源進行抽象描述,那麼必然要初始化數據,好比一個隊列Queue,有屬性size,這是一個抽象的數據結構,那麼一個具體的隊列到底有多大?你須要設置size 的值,因此對於管程還包括 初始化
image_5c567cef_1d53
一個基本的管程定義以下:
Minitor{
  管程內部的變量結構以及說明;
  函數1(){

  }
  ......
  函數N(){

  }
  init(){
       對管程中的局部變量進行初始化;
  }
}

管程特色

管程就是管理進程,管程的概念就是設計模式中「依賴倒置原則」,依賴倒置原則是軟件設計的一個理念,IOC的概念就是依賴倒置原則的一個具體的設計
管程將對共享資源的同步處理封閉在管程內,須要申請和釋放資源的進程調用管程,這些進程再也不須要自主維護同步。有了管程這個大管家(門面模式?)進程的同步任務將會變得更加簡單。
管程是牆,過程是門,想要訪問共享資源,必須經過管程的控制(經過城牆上的門,也就是通過管程的過程)
而管程每次只准許一個進程進入管程,從而實現了進程互斥
image_5c567cef_6ac2
管程的核心理念就是至關於構造了一個管理進程同步的「IOC」容器。
 
管程是一個語言的組成成分(非操做系統支持部分),管程的互斥訪問徹底由編譯程序在編譯時自動添加上,無需程序員關心,並且保證正確
通常的 monitor 實現模式是編程語言在語法上提供語法糖,而如何實現 monitor 機制,則屬於編譯器的工做
好比,Java中使用synchronized時,這是否是一種管程理念?你只是寫了一個synchronized關鍵字(語法糖),多線程的共享同步徹底不用你操心了
(注意: 並非全部的語言都支持管程的概念
image_5c567cef_476e

條件變量

管程能夠保證互斥,同一時刻僅有一個進程進入管程,因此他必然須要同步工具,如兩個同步操做原語 wait和 signal,他還須要互斥量用以控制管程進入的同步
當某進程經過管程請求得到臨界資源而未能知足時,管程便調用 wait 原語使該進程等待,並將其排在等待隊列上
僅當另外一進程訪問完成並釋放該資源以後,管程調用signal原語,喚醒等待隊列中的隊首進程 
 
可是,僅僅這個互斥量是不夠的
好比,若是須要處理以前提到過的「執行順序控制」,如何控制前驅關係?
當一個進程調用了管程,在管程中時被阻塞或掛起,直到阻塞或掛起的緣由解除,而在此期間,若是該進程不釋放管程,則其它進程沒法進入管程,被迫長時間地等待。
因此還須要其餘的信號量用於針對其餘條件進行同步,這些就是條件變量,因此一個完整的管程定義爲:
Minitor{
  管程內部的變量結構以及說明;
  condition  條件變量列表;
  函數1(){

  }
  ......
  函數N(){

  }
  init(){
          對管程中的局部變量進行初始化;
  }
}
條件變量就是當調用管程過程的進程沒法運行時,用於阻塞進程的一種信號量
 
管程中對每一個條件變量都須予以說明,其形式爲:Var x,y:condition。
對條件變量的操做僅僅是wait和signal,條件變量也是一種抽象數據類型,每一個條件變量保存了一個鏈表,用於記錄因該條件變量而阻塞的全部進程,同時提供的兩個操做便可表示爲 x.wait和x.signal,其含義爲: 
①   x.wait:正在調用管程的進程因 x 條件須要被阻塞或掛起,則調用 x.wait 將本身插入到x條件的等待隊列上,並釋放管程,直到x條件變化。此時其它進程可使用該管程。  
②   x.signal:正在調用管程的進程發現 x 條件發生了變化,則調用 x.signal,從新啓動一個因 x 條件而阻塞或掛起的進程。若是存在多個這樣的進程,則選擇其中的一個,若是沒有,則繼續執行原進程,而不產生任何結果。這與信號量機制中的 signal操做不一樣,由於後者老是要執行s:=s+1操做,於是總會改變信號量的狀態。
 
若是有進程Q因x條件處於阻塞狀態, 當正在調用管程的進程P執行了x.signal操做後,進程Q 被從新啓動,此時兩個進程 P和Q,如何肯定哪一個執行,哪一個等待,可採用下述兩種方式之一進行處理: 
(1)  P等待,直至Q 離開管程或等待另外一條件。
(2)  Q等待,直至P離開管程或等待另外一條件。
採用哪一種處理方式,固然是各執一詞。
Hoare 採用了第一種處理方式
而 Hansan 選擇了二者的折衷,他規定管程中的過程所執行的signal  操做是過程體的最後一個操做,因而,進程P執行signal操做後當即退出管程,於是進程Q立刻被恢復執行。 

總結

進程控制是操做系統的一種硬性管理,是必需要有的,若是沒有進程控制,就沒辦法合理安排運行進程, 根本沒法完成狀態的切換維護等。
進程的同步是一種軟邏輯上的,若是不作,並不會致使系統出問題或者進程沒法運行,可是若是不進行同步,結果卻極可能是錯誤的,因此也是必須作的
類比裝修的話,進程控制就是硬裝,不裝無法住,總歸要水電搞搞好,進程同步就是傢俱家電和軟裝,硬裝後住進去不會出現「生存問題」(至少有水喝有電用),可是你要是連個熱水壺都沒有是打算要喝涼水麼
進程同步的概念多很複雜抽象,由於畢竟是概念表述,沒有涉及到具體的實現細節。
進程同步的核心是對於臨界資源的訪問控制,也就是對於臨界區的訪問。
無論是臨界區算法仍是信號量機制仍是管程機制,終歸也都是控制進入臨界區的不一樣的實現思路。
每種不一樣的算法、機制都各自有各自的特點,場景。
信號量機制將臨界資源的訪問的互斥,演化爲能夠多個進程訪問資源(整型信號量),記錄型信號量對整型信號量機制進行優化,處理了忙等的問題 
而後繼續演化出AND型,能夠對不一樣的資源進行同步,而不只僅是同一種資源
最後發展爲信號量集的形式,能夠對不一樣的資源、不一樣的個數、不一樣的限制進行處理,變得更爲通用。
 
管程更多的是一種設計思惟,管程就是管理進程的程序,進程對於資源的同步操做全都依賴管程這一「大管家」,管程是編程語言級別的,不須要程序員進行過多的處理,通常會提供語法糖
須要注意並非全部的語言都有管程的概念(Java是有的),管程讓你從同步的細節中解放出來,能夠在不少場景下簡化同步的實現。
管程的概念是「線程同步」的「IOC」,大大簡化了同步的代價。
無論臨界區算法仍是信號量機制仍是藉助於管程,他們都是一種同步工具,能夠認爲他們就是一組「方法」,「方法」的邏輯就是本文前面介紹的原理
在須要進程同步問題的解決思路中,能夠直接使用「封裝好的方法」
 
以上,儘管都是在說操做系統關於進程的同步的處理,其實,在同步問題上進程和線程的設計理念是相通的
由於這本質上都是在說併發----多道程序運行的操做系統,一般使用輪轉時間片的方式併發的運行多個進程 
相關文章
相關標籤/搜索