因爲進程具備獨立性和異步性等併發特徵,計算機的資源有限,致使了進程之間的資源競爭和共享,也致使了對進程執行過程的制約。數據結構
臨界資源:一次只能供一個進程訪問的資源。
臨界區:把不容許多個併發進程交叉執行的一段程序稱爲臨界區(critical region)或臨界部分(critical section)。併發
臨界區是由屬於不一樣併發進程的程序段共享公用數據或公用數據變量而引發的,臨界區不可能用增長硬件的方法來解決。所以,臨界區也能夠被稱爲訪問公用數據的那段程序。異步
當一個進程使用該臨界資源時,其餘須要訪問該資源的進程必須阻塞,直到佔用者釋放該資源。測試
把這種因爲共享某一公有資源而引發的在臨界區內不容許併發進程交叉執行的現象,稱爲由共享公有資源而形成的對併發進程執行速度的間接制約。這裏的「間接」二字主要是指各併發進程的速度受公有資源的制約,而非進程之間的直接制約。操作系統
互斥:一組併發進程中的一個或多個程序段,因共享某一公有資源而致使它們必須以一個不容許交叉執行的單位執行。也就是說,不容許兩個以上的共享該資源的併發進程同時進入臨界區。設計
通常狀況下,做爲程序段的一個過程不容許多個進程同時訪問它。但若是該過程是純過程,則各併發進程能夠同時訪問它。純過程是指在執行過程當中不改變過程自身代碼的一類過程。指針
一組併發進程互斥執行時必須知足以下準則:code
對臨界區加鎖以實現互斥。當某個進程進入臨界區後,它將鎖上臨界區,直到它退出臨界區爲止。併發進程在申請進入臨界區時,首先測試該臨界區是否上鎖。隊列
加鎖實現中的lock(key[S])和unlock(key[S])均爲原子操做。有一點須要注意的是:在系統實現時鎖定位key[S]老是設置在公有資源所對應的數據結構中的。進程
加鎖實現的缺點:
A: lock(key[S]) <S> unlock(key[S]) Goto A
進程PB:
B: lock(key[S]) <S> unlock(key[S]) Goto B
因爲兩個進程中均有Goto語句(Goto A和Goto B),這就有可能出現其中一個進程的執行,致使另外一個進程長期得不處處理機資源,而處於永久飢餓狀態(starvation)。
分析能夠知道,一個進程可否進入臨界區取決於進程本身調用lock過程去測試相應的鎖定位。也就是說,每一個進程可否進入臨界區是依靠進程本身的測試判斷。這樣,沒有得到執行機會的進程固然沒法判斷,從而出現不公平現象。
那麼是否有辦法解決這個問題呢?固然,很明顯,辦法是有的,咱們能夠爲臨界區設置一個管理員,由這個管理員來管理相應臨界區的公有資源,它表明可用資源的實體,這個管理員就是信號量。
信號量和P、V原語是荷蘭科學家E. W. Dijkstra提出來的。
【信號量】 在操做系統中,信號量sem是一個整數。
顯然,用於互斥的信號量sem的初值應該大於0,而創建一個信號量必須說明所建信號量表明的意義,賦初值,以及創建相應的數據結構,以便指向那些等待使用該臨界區的進程。
【P、V原語】
信號量的數值僅能由P、V原語操做改變。採用P、V原語,能夠把類名爲S的臨界區描述爲:When S do P(sem) 臨界區 V(sem) od。
P原語操做:
V原語操做:
這裏給出一個使用加鎖法的軟件實現方法來實現P、V原語:
P(sem): begin 封鎖中斷; lock(lockbit) val[sem]=val[sem]-1 if val[sem]<0 保護當前進程CPU現場 當前進程狀態置爲「等待」 將當前進程插入信號sem等待隊列 轉進程調度 fi unlock(lockbit);開放中斷 end
V(sem): begin 封鎖中斷; lock(lockbit) val[sem]=val[sem]+1 if val[sem]<=0 local k 從sem等待隊列中選取一個等待進程,將其指針置入k中 將k插入就緒隊列 進程狀態置位「就緒」 fi unlock(lockbit);開放中斷 end
設信號量sem是用於互斥的信號量,且其初始值爲1表示沒有併發進程使用該臨界區。顯然,由前面論述可知,只要把臨界區置於P(sem)和V(sem)之間,便可實現進程之間的互斥。
用信號量實現兩個併發進程PA和PB互斥的描述以下:
(1)設sem爲互斥信號量,其取值範圍爲(1,0,-1)。其中sem=1表示進程PA和PB都未進入類名爲S的臨界區,sem=0表示進程PA或PB已進入類名爲S的臨界區,sem=-1表示進程PA和PB中,一個進程已進入臨界區,而另外一個進程等待進入該臨界區。
(2)實現過程:
Pa: P(sem) <S> V(sem) . . .
Pb: P(sem) <S> V(sem) . . .
【進程間的直接制約】:一組在異步環境下的併發進程,各自的執行結果互爲對方的執行條件,從而限制各進程的執行速度的過程稱爲併發進程間的直接制約。這裏的異步環境主要是指各併發進程的執行起始時間的隨機性和執行速度的獨立性。
【進程間的同步】:把異步環境下的一組併發進程因直接制約而互相發送消息而進行互相合做、互相等待,使得各進程按必定的速度執行的過程稱爲進程間的同步。
具備同步關係的一組併發進程稱爲合做進程,合做進程間相互發送的信號稱爲消息或事件。
用消息實現進程同步:
用 wait(消息名) 表示進程等待合做進程發來的消息。 用 signal(消息名) 表示向合做進程發送消息。 過程wait的功能是等待到消息名爲true的進程繼續執行,而signal的功能則是向合做進程發送合做進程所須要的消息名,並將其值置爲true。
【進程互斥和進程同步】:進程同步不一樣於進程互斥,進程互斥時它們的執行順序能夠是任意的。通常來講,也能夠把個進程之間發送的消息做爲信號量看待。與進程互斥時不一樣的是,這裏的信號量只與制約進程及被制約進程有關,而不是與整租併發進程有關。所以,稱該信號量爲私用信號量(private semaphore)。一個進程Pi的私用信號量semi是從制約進程發送來的進程Pi的執行條件所須要的信息。與私用信號量相對應,稱互斥時使用的信號量爲公用信號量。
【用P、V原語實現進程同步】:
首先爲各併發進程設置私用信號量,而後爲私用信號量賦初值,最後利用P、V原語和私用信號量規定各進程的執行順序。
把併發進程的同步和互斥問題通常化,能夠獲得一個抽象的通常模型,即生產者-消費者問題(producer-consumer problems)。
把系統中使用某一類資源的進程稱爲該資源的消費者,而把釋放同類資源的進程稱爲該資源的生產者。
設生產者進程和消費者進程是互相等效的,其中,各生產者進程使用的過程deposit(data)和各消費者使用的進程remove(data)可描述以下:
首先,能夠看到,上述生產者-消費者問題是一個同步問題。即生產者和消費者之間知足以下條件:
另外,因爲有界緩衝區是臨界資源,所以,各生產者進程和各消費者進程之間必須互斥執行。
設公用信號量mutex保證生產者進程和消費者進程之間的互斥,設信號量avail爲生產者進程的私用信號量,信號量full爲消費者進程的私用信號量。信號量avail表示有界緩衝區中的空單元數,初值爲n;信號量full表示有界緩衝區中非空單元數,初始值爲0。信號量mutex表示可用有界緩衝區的個數,初始值爲1。
deposit(data): begin P(avail) // 檢查是否有空單元可使用 P(mutex) // 檢查臨界區是否被佔用 送數據入緩衝區某單元 V(full) V(mutex) end
remove(data): begin P(full) // 檢查是否有非空單元 P(mutex) // 檢查臨界區 取緩衝區中某單元數據 V(avail) V(mutex) end
在這個例子中,因爲包含多個公用信號量和私用信號量,所以P、V原語的操做順序須要很是當心。通常來講,因爲V原語是釋放資源,因此能夠以任意次序出現。但P原語則否則,若是次序混亂,將會形成進程之間的死鎖。例如:若是咱們調換P(full)和P(mutex),便有可能致使程序一直佔用臨界區,而沒法獲得資源(若是此時full=0,mutex >= 0,程序進入臨界區後得不到資源,但又沒法釋放臨界區),從而致使死鎖。
【其餘】 設計:先看是否存在互斥,給出公用信號量;再看同步,給出私用信號量。互斥和同步分開操做。 操做:先對同步的私用信號量作P操做,而後對互斥的公用信號量作P操做。