420 likes | 735 Views
第五章 並行:互斥與同步 Concurrency: Mutual Exclusion and Synchronization. 5.1 並行的原理 並行執行的問題 所有 process 可共享的全域性資源負擔著風險。例如:對全域性變數執行讀寫動作,讀寫順序變得很重要。 最理想的資源分配管理,對於系統而言是困難的。例如: process A 要求鎖住某特定 I/O 通道,但在使用該通道前遭暫停 (suspend) ,對系統而言,如此並沒有效益。 程式設計錯誤的確認變得困難。 ( 執行結果不具重複性 ). 簡單的範例:交錯執行共用變數的程式.
E N D
第五章 並行:互斥與同步Concurrency: Mutual Exclusion and Synchronization 5.1 並行的原理 • 並行執行的問題 • 所有process可共享的全域性資源負擔著風險。例如:對全域性變數執行讀寫動作,讀寫順序變得很重要。 • 最理想的資源分配管理,對於系統而言是困難的。例如:process A 要求鎖住某特定I/O通道,但在使用該通道前遭暫停(suspend),對系統而言,如此並沒有效益。 • 程式設計錯誤的確認變得困難。(執行結果不具重複性)
簡單的範例:交錯執行共用變數的程式 Process P1Process P2 chin = getchar(); chin = getchar(); chout = chin; putchar(chout); chout = chin; putchar(chout); void echo() { chin = getchar(); chout = chin; putchar(chout); } (chin = x) (chin = y) (chout = y)
簡單的範例 (續):臨界區間的阻擋 Process P1Process P2 chin = getchar(); (P1仍在echo程序,P2被阻擋) chout = chin; putchar(chout); (P1離開echo,P2的阻擋消除) chin = getchar(); chout = chin; putchar(chout); (chin = x) (chin = y)
簡單的範例 (續):在二個不同的處理器執行 Process P1Process P2 chin = getchar(); chin = getchar(); chout = chin; chout = chin; putchar(chout); putchar(chout); (chin = x) (chin = y)
作業系統考量:設計和管理的問題 • 作業系統有能力追蹤各個活動的process。(利用process的控制區塊) • 作業系統具有替活動的process分配和取回資源的能力。 • 這些資源包括:處理器時間、記憶體、檔案、I/O裝置。 • 作業系統必須保護每個process的資料和資源。 • process執行的結果必須和執行速度無關(speed independence),且不受其他process執行速度的影響。
Process的交互作用 • 三種感知程度(degrees of awareness): • 無法感知彼此的存在: 獨立的process。 • 間接感知彼此的存在: 共享某些物件的存取權(例如,I/O緩衝區)。 • 直接感知彼此的存在: 設計來一起完成某些動作。 • Process彼此間對資源競爭的情形下,三個控制問題: • Mutual Exclusion (互斥):每次只有一個程式在臨界區域(critical section)。 • Deadlock (死結):例如:Process P1, P2; 資源 R1, R2; P1佔有R2, P2佔有R1。假設需要同時二個資源才能完成動作,則發生死結。 • Starvation (飢餓):假設P1, P2, P3需定期使用資源R,P1使用R時,P2、P3被耽擱(delayed)。當P1離開臨界區域時,假設P3獲得R的使用權,如此週而復始,P2永遠無法取得資源R。
Mutual Exclusion(互斥)機制:進出臨界區域 /* program mutualexclusion */ const int n = …; {* number of processes *} void P(int i) { while(true) { entercritical(i); /* critical section */ exitcritical(i); /* remainder */ } } void main() { parbegin(P(R1), P(R2), …, P(Rn)); }
資料的一致性(coherence) • 考慮一個簿記應用程式的許多資料項目可能被修改 • 資料a 和 b,被維持在 a = b 的關係下。(例如: a = b = 1) • 考慮以下二個process: P1: a = a + 1; b = b + 1; P2: b = 2 * b; a = 2 * a; • 假設此時二個process分別要求了個別資料項(a及b)的互斥﹔ a = a + 1; b = 2 * b; b = b + 1; a = 2 * a; (結果: a = 4 及 b = 3)
Mutual Exclusion的需求(Requirements) • 任何時間只允許一個process在臨界區域執行。 • Only one process at a time is allowed in the critical section for a resource • 在臨界區域停止執行的process,不准影響其他process執行。 • If a process halts in its critical section, it must not interfere with other processes. • 不准無止盡的耽擱一個要求進入臨界區域的process,不能有死結或飢餓發生 。 • A process requiring the critical section must not be delayed indefinitely; no deadlock or starvation • 當沒有任何Process處於臨界區域時,任何要求進入臨界區域的process必須立刻得到進入的允許。 • 不可對Process的相對速度和處理器的數目做任何假設。 • Process只能在臨界區域內停留一有限的時間。
5.2 Mutual Exclusion(互斥):軟體處理方式 • 任何對mutual exclusion的嘗試,都必須仰賴硬體上一些基本的排斥機制。 • 常見的機制:對記憶體位址的存取,一次只能有一個的限制。(在單一處理器或SMP皆如此) • 首先考慮2個process: P0 及 P1 • Dekker演算法前四次嘗試均不正確。(說明Mutual Exclusion問題的難度及可能的邏輯盲點) • Dekker演算法, 正確解法。 • Peterson演算法簡化Dekker演算法, 正確解法。
Dekker演算法, 第一次嘗試 • 共享的全域變數: int turn = 0; (英文: It’s your turn.) 執行權在0, 1間交換。 • 忙碌等待 (busy-waiting): while-loop • 保證mutual exclusion • 缺點: • P0, P1必須全然輪流使用臨界區域,執行步調受到較慢的process影響。 • 如果一個process失敗(無論是臨界區域內或外),另一process將永遠被堵住。
Dekker演算法, 第二次嘗試 • 共享全域變數: boolean flag[2] = {false, false}; • 每個process有自己的flag:flag[0]或flag[1]。每個process可檢查另一process的flag,但不能更改資料。 • 當一個process希望進入臨界區域時,它週期性檢查其他的flag直到其值為false(表示另一個process不在臨界區域內)。 • 此時它設定自己的flag為true ,然後進入臨界區域。離開時,設定自己的flag為false。
缺點:甚至不保證互斥。舉例: • P0執行到while,發現flag[1]為false • P1執行到while,發現flag[0]為false • P0設定flag[0]為true,進入臨界區域。 • P1設定flag[1]為true,進入臨界區域。
Dekker演算法, 第三次嘗試 • 透過改變二行程式碼的位置,嘗試解決問題。不過仍然失敗! • 缺點:死結。舉例: • P0設定flag[0]為true。 • P1設定flag[1]為true 。 • P0執行到while,發現flag[1]為true,忙碌等待。 • P1執行到while,發現flag[0]為true,忙碌等待。
相當接近正確答案,但還有瑕疵: • P0設定flag[0]為true。 • P1設定flag[1]為true 。 • P0執行while (flag[1]為true) ,進入while迴圈主體。 • P1執行while (flag[0]為true) ,進入while迴圈主體。 • P0設定flag[0]為false。延遲。 • P1設定flag[1]為false。延遲。 • P0設定flag[0]為true。 • P1設定flag[1]為true。 • 嚴格來說不是死結(任何一個process改變相對執行速度,就可打破此無窮迴路)
Dekker演算法, 正確解法 boolean flag [2]; int turn; void P0() { while (true) { flag [0] = true; while (flag [1]) if (turn == 1) { flag [0] = false; while (turn == 1) /* do nothing */; flag [0] = true; } /* critical section */; turn = 1; flag [0] = false; /* remainder */; } } void P1(){/*0改為1,1改為0,其餘相同*/} • 利用嘗試一的turn,避免相互禮讓的情形。 • P0想進入臨界區域時,更改flag[0]為真。 • 如果flag[1] 是假,P0進入臨界區域。 • 否則就由turn決定:turn=0,輪由P0堅持進入,P0不斷檢查flag[1] ,直到P1鬆手; • turn=1,P0將flag[0]改為假,鬆手讓P1前進。
Peterson演算法 • Dekker演算法不易推廣到更多process,且證明其正確性需要很高的技巧。 • Peterson[1981]提供一簡單絕妙的解法。 • P0進入臨界區域的條件:flag[1]為false或turn=0 • P0設turn=1,相互禮讓。最後結果看禮讓的順序。 boolean flag [2]; int turn; void P0() { while (true) { flag [0] = true; turn = 1; while (flag [1] && turn == 1) /* do nothing */; /* critical section */; flag [0] = false; /* remainder */; } } void P1() {/* 0改為1, 1改為0, 其餘相同 */}
5.3 Mutual Exclusion(互斥):硬體支援 • 使中斷無效:在單一處理器機器,並行process只能交錯執行,只要防止process被中斷就可以了。 • 系統核心可以提供這種primitives。 • 因為臨界區域不准被中斷,可以保證mutual exclusion。 • 缺點: • 執行效能退化,處理器介入程式的能力受限制。 • 在多處理器上,不允許中斷,並不能保證mutual exclusion。 while (true) { /* disable interrupts, 關閉中斷 */ /* critical section */ /* enable interrupts, 打開中斷 */ /* 其餘程式 */ }
特殊的機器指令:測試和設定(test-and-set) • 如果i值為0,則i值改為1,並返回true;否則不改i值。 • 整個指令不能被中斷。 • 離開臨界區間後,將bolt (門閂)設成0。 /* program mutualexclusion */ const int n = /*number of processes */; int bolt; void P(int i) { while (true) { while (!testset (bolt)) /* do nothing */; /* critical section */; bolt = 0; /* remainder */ } } void main(){ bolt = 0; parbegin (P(1),P(2), …,P(n)); } boolean testset(int &i) { if (i == 0) { i = 1; return true; } else return false; }
交換的指令(exchange) • keyi: 區域變數 • 先設keyi = 1, 利用exchange(keyi, bolt)設定bolt = 1。 • 若keyi=0,表門閂未上鎖,即進入臨界區間。 /* program mutualexclusion */ int const n = /* number of processes**/; int bolt; void P(int i) { int keyi; while (true) { keyi = 1; while (keyi != 0) exchange (keyi, bolt); /* critical section */; exchange (keyi, bolt);/* bolt=0 */ /* remainder */ } } void main() { bolt = 0; parbegin (P(1), P(2), …, P(n)); } void exchange( int ®, int &mem) { int temp; temp = mem; mem = reg; reg = temp; }
機器指令方法的特性 • 優點 • 無論幾個process,單一處理器或對稱式多處理器,它都適用。 • 可支援多個臨界區域,每個臨界區域可以用自己的變數。 • 缺點 • 採用忙碌等待(busy-waiting),每一process在等待進入臨界區域,都會消耗處理器的時間。 • 可能發生飢餓。隨機選擇下一個進入臨界區域的process,可能有process一直無法進入。 • 可能發生死結。P1進入臨界區域,P2有較高優先權,P1被迫中斷,若P2也要進入臨界區域,將等待P1,形成死結。
5.4 號誌(Semaphore) • 基本的原理:透過簡單的訊號,多個process可以彼此(複雜的)合作。 • 訊號化使用的特殊變數,叫做號誌[Dijkstra 1965] 。 • 傳遞訊號 s:signal(s) (Dijkstra原始論文中稱 P operation) • 接收訊號 s:wait(s) (V operation) • 號誌是一整數變數,以下三個動作: • 初設成非負數的值。 • wait動作會減少號誌的數值。如果數值成為負數,執行wait動作的process會被懸置(blocked)。 • signal動作會增加號誌的數值。如果數值不是正整數,則因wait動作懸置的process就會被解除懸置。 • wait 及 signal是atomic (最小的、原子的) ;不能被中斷。 • 二元號誌:只能0和1的數值。與一般號誌有相同表達能力。
號誌的定義、利用號誌達到互斥 struct semaphore { int count; queueType queue; }; void wait(semaphore s) { s.count--; if (s.count < 0) { place this process in s.queue; block this process; } } void signal(semaphore s) { s.count++; if (s.count <= 0) { remove a process P from s.queue; place process P on ready list; } } /* program mutualexclusion */ const int n = /* number of processes */; semaphore s = 1; void P(int i) { while (true) { wait(s); /* critical section */; signal(s); /* remainder */; } } void main() { parbegin (P(1), P(2), …, P(n)); } 圖5.9 利用號誌達到互斥 圖5.6 號誌的定義
Figure 5.10 Processes Accessing Shared Data Protected by a Semaphore 25
s.count 0: s.count是可以執行wait(s)而不會懸置的process的個數。 • s.count < 0: s.count的大小是懸置在s.queue的process的個數。
生產者/消費者問題(the producer/consumer problem) • 一個process產生出某種形式的資料,放在緩衝區;有一消費者從緩衝區中,一次拿出一個物件。 • 系統為避免緩衝區重疊使用,限制一次只允許一方(生產者或消費者)進出緩衝區。 producer: while (true) { /* produce item v; */ b[in] = v; in ++; } consumer: while (true) { while (in <= out) /* do nothing */ w = b[out]; out ++; /* consume item w */ } Figure 5.11 Infinite Buffer for the Producer/Consumer Problem (灰色表目前佔用)
有限循環緩衝區 Figure 5.15 Finite Circular Buffer for the Producer/Consumer Problem
用號誌解決生產者/消費者問題 /* program producerconsumer */ semaphore n = 0; semaphore s = 1; void producer() { while (true) { produce(); wait(s); append(); signal(s); signal(n); } } void consumer() { while (true) { wait(n); wait(s); take(); signal(s); consume(); } } void main() { parbegin (producer, consumer); } Figure 5.14 A Solution to the Infinite-Buffer Producer/Consumer Problem Using Semaphores
理髮店問題(a barbershop problem) 理髮師的狀態:理髮、收費、坐在椅子上睡覺等顧客上門 入口 三張椅子,三位理髮師 一個收銀機 站立的空間,消防法令規定顧客上限20人 四個顧客坐著等待 出口 Figure 5.19 The Barbershop
5.5 監督器(Monitors) • 號誌的缺點:wait 及 signal 動作很powerful且很有彈性,然而使用號誌很難製作出正確的解法。wait及signal動作可能分散在整個程式中,很難看出整個動作的影響效果。 • Monitor [Hoare 1974]是一個程式語言的架構(construct) ,提供和號誌相同的效果,並且容易控制。 • 監督器結合訊號(Monitor with Signal)特性: • 區域變數只能被monitor的程序所存取。 • process藉由呼叫monitor的程序以進入該monitor。 • 同時間內只有一個process可在monitor中執行;任何其他process都被暫停(阻擋),以等待monitor變回可用狀態。 • monitor使用條件變數(condition variable)來支援同步。 • cwait(c):目前呼叫的process等待條件c。 • csignal(c):因等待條件c而暫停的process之一繼續執行。若沒有process在等待,訊號將消失。
Monitor的結構 等待進入的process 單一進入點 區域資料 執行cwait暫停自己 條件變數 程序 1 初始化程式碼 Figure 5.21 Structure of a Monitor 32
/* program producerconsumer */ monitor boundedbuffer; char buffer [N]; /* space for N items */ int nextin, nextout; /* buffer pointers */ int count; /* number of items in buffer */ int notfull, notempty; /* for synchronization */ void append (char x) { if (count == N) cwait(notfull); /* buffer is full; avoid overflow */ buffer[nextin] = x; nextin = (nextin + 1) % N; count++; /* one more item in buffer */ csignal(notempty); /* resume any waiting consumer */ } void take (char x) { if (count == 0) cwait(notempty); /* buffer is empty; avoid underflow */ x = buffer[nextout]; nextout = (nextout + 1) % N; count--; /* one fewer item in buffer */ csignal(notfull); /* resume any waiting producer */ } 33
void producer() { char x; while (true) { produce(x); append(x); } } void consumer() { char x; while (true) { take(x); consume(x); } } voidmain() { parbegin (producer, consumer); } Figure 5.22 A Solution to the Bounded-Buffer Producer/Consumer Problem Using a Monitor 34
5.6 訊息傳遞(Message Passing) • Process之間互動的基本需求: • 同步:mutual exclusion。 • 通訊:交換資訊。 • 訊息傳遞可同時提供這兩個功能 • 可在分散式系統、對稱式多處理器、單處理器的系統實作 • 訊息傳遞系統的形式 • 傳送:send(目的,訊息) • 接收:receive(來源,訊息) • 二個process間訊息的聯繫暗示某種程度的同步: • 接收者不能接收訊息直到其他process傳送訊息給它。
訊息系統針對Process間通訊的設計特質 • 傳送與接收都可為阻擋或未阻擋,常見的3種組合: • blocking send, blocking receive:稱為約會(rendezvous) ,允許process間緊密地同步。 • non-blocking send, blocking receive: 最有用的組合。 • non-blocking send, non-blocking receive:都不必等待。 • 一般訊息的格式
郵件信箱與訊息埠 信箱 • 彈性的使用訊息 • 系統另外提供建立信箱的服務 • 埠為Q1所擁有 埠
利用訊息達到互斥 /* program mutualexclusion */ const int n = /* number of processes */; void P(int i) { message msg; while (true) { receive (mutex, msg); /* critical section */; send (mutex, msg); /* remainder */; } } void main() { create_mailbox (mutex); send (mutex, null); parbegin (P(1), P(2), . . ., P(n)); } Figure 5.26 Mutual Exclusion Using Messages
利用訊息之生產者/消費者問題解法 const int capacity = /* buffering capacity */ ; null = /* empty message */ ; int i; void producer() { message pmsg; while (true) { receive (mayproduce, pmsg); pmsg = produce(); send (mayconsume, pmsg); } } void consumer() { message cmsg; while (true) { receive (mayconsume, cmsg); consume (cmsg); send (mayproduce, null); } }
void main() { create_mailbox (mayproduce); create_mailbox (mayconsume); for (int i = 1; i <= capacity; i++) send (mayproduce, null); parbegin (producer, consumer); } Figure 5.27 A Solution to the Bounded-Buffer Producer/Consumer Problem Using Messages
5.7 讀取者/寫入者(古典)問題 • 問題定義: • 一資料區域由許多process所分享,許多process只讀資料區域(讀取者) ,而許多process只寫入資料區域(寫入者) 。必須滿足下列狀況 • 任何數目的讀取者可同時讀取檔案。 • 一次只有一個寫入者可以寫入檔案。 • 假如寫入者正在寫入檔案,則沒有任何讀取者可以讀取。