350 likes | 571 Views
行程的同步. 註:本章學術性較重,但考試常考。. 為何要同步 ?. 為何要同步 ? 避免競 爭情況 (Race Condition) 方法:鎖定臨界區間 (Critical Section) 低階的同步方法 交替演算法 (Turn Algorithm) 旗標演算法 (Flag Algorithm) 綜合演算法 (Combination Algorithm) 麵包店演算法 (Bakery Algorithm) 硬體支援 (Hardware Support) 禁止中斷 ( 在臨界區間內 ) 硬體的 Test and set 指令
E N D
行程的同步 註:本章學術性較重,但考試常考。
為何要同步? • 為何要同步? • 避免競爭情況 (Race Condition) • 方法:鎖定臨界區間 (Critical Section) • 低階的同步方法 • 交替演算法 (Turn Algorithm) • 旗標演算法 (Flag Algorithm) • 綜合演算法 (Combination Algorithm) • 麵包店演算法 (Bakery Algorithm) • 硬體支援 (Hardware Support) • 禁止中斷 (在臨界區間內) • 硬體的 Test and set 指令 • 硬體的 swap指令 • 高階的同步方法 • 號誌 (Semaphore) • 計數號誌 (Counting Semaphore) • 二元號誌 (Binary Semaphore) -- Java • 臨界區域 (Critical Region) • 監督程式 (Monitor) – C# 2
競爭情況 (Race Condition) Consumer Producer while (true) { while (counter == BUFFER_SIZE) ; buffer[in] = nextProduced; in = (in+1) % BUFFER_SIZE; counter = counter + 1; } while (true) { while (counter == 0) ; nextConsumed = buffer[out]; out = (out+1) % BUFFER_SIZE; counter = counter - 1; } 3
競爭情況 (Race Condition) Producer : counter++ Consumer : counter-- LOAD register, counter; ADD register, 1 STORE counter, register LOAD register, counter; SUB register, 1 STORE counter, register P: LOAD register, counter; P: ADD register, 1 C: LOAD register, counter C: SUB register, 1 P: STORE counter, register C: STORE counter, register register = 5 ; register = 6 register = 5 register = 4 counter = 5 counter = 4 If counter = 5 then counter may be 4 5 6 After execution 4
避免競爭 1 : 臨界區間 • 上述的會產生競爭情況的段落,不能讓多個程式同時執行 • 為了確保執行順序不會出問題,我們可以採用臨界區間的方式 do { entry section critical section exit section remainder section } while (1) 5
競爭情況的解決方法 • 方法一:用硬體解決 • 單一CPU : • 在一個關鍵段落還沒完成之前,禁止中斷的發生 (Intel 80x86,ARM…) • 利用 test and set 等硬體指令 • 多個CPU : (雙核心以上) • 必須加上旋轉鎖機制 (Linux, Windows) (busy waiting) • 方法二:用軟體解決 • 交替演算法 (Turn Algorithm) • 旗標演算法 (Flag Algorithm) • 綜合演算法 (Combination Algorithm) • 麵包店演算法 (Bakery Algorithm) 6
競爭情況的解決方法必需滿足下列條件 • 互斥 (Mutual Exclusion) • 進行 (Progress) • 有限等待 (Bounded Waiting) 7
交替演算法 • 兩個行程 Pi及 Pj 之間的臨界區演算法。 • 共用一個變數 turn,指出目前允許進入臨界區的是哪一個行程。 • 只記錄系統目前的狀態,但是並不記錄行程個別的狀態。 • 如果 turn = i,代表行程 Pi可以進入臨界區之中。 • 滿除臨界區互斥的條件,但是不能滿足進行的條件。 8
do { while (turn != i) ; critical section turn = j; remainder section } while (1); 交替演算法 (續) • 行程Pi的程式結構如下: 9
旗標演算法 • 兩個行程Pi及 Pj之間的臨界區演算法。 • 將交替演算法中共有的變數 turn 改為共有的陣列 flag,記錄系統中個別行程的狀態。 • 如果 Pi要進入臨界區,則將 flag[i] 設為 TRUE。 • 當 Pi離開臨界區後,再將 flag[i] 設為 FALSE。 • 滿足臨界區互斥的條件,但是仍然無法滿足進行的條件。 10
do { flag[i] = TRUE; while (flag[j]) ; critical section flag[i] = false; remainder section } while (1); 旗標演算法 (續) • 行程 Pi的程式結構如下: 11
綜合演算法 • 兩個行程 Pi及 Pj 之間的正確臨界區演算法。 • 綜合交替演算法與旗標演算法。 • 以 flag 陣列記錄個別行程是否想要進入臨界區;而 turn 變數指出目前系統允許哪個行程進入臨界區。 • 同時滿足互斥、有限等待與進行三個條件。 12
綜合演算法 (續) • 行程 Pi的程式結構如下: do { flag[i] = TRUE; turn = j; while (flag[j] && turn == j) ; critical section flag[i] = false; remainder section } while (1); 13
麵包店演算法 • 多個行程間的臨界區演算法。 • 以行程取到的號碼牌,由小而大地讓行程進入臨界區中。 • 若有行程取到相同號碼,則以行程的 ID 大小決定先後順序,ID 較小的優先。 • 同時滿足互斥、有限等待與進行三個條件。 14
麵包店演算法 (續) • 行程 Pi的程式結構如下: do { choosing[i] = TRUE; number[i] = max(number[0], number[1], …, number[n – 1]) + 1; choosing[i] = FALSE; for (j = 0; j < n; j++) { while (choosing[j]) ; while ((number[j] != 0) && ((number[j], j) < (number[i], i))) ; } critical section number[i] = 0; remainder section } while (1); 15
硬體支援1 : 禁止中斷 • 單 CPU 的系統 • 讓行程在修改共用的變數時停止接受中斷而解決同步的問題。 • 多 CPU 的系統 • 加上旋轉鎖等忙碌等待機制 (busy waiting) 17
硬體支援2 : Test-and-Set 指令 • 在執行完整個指令之前都不會被中斷。 • Test-and-Set 指令會傳回參數 target 目前的值,並同時將 target 的值設為 TRUE。 • 可以利用 Test-and-Set 指令實作多行程的臨界區演算法。 boolean Test-and-Set (Boolean &target) { Boolean rv = target; target = true; return rv; } 18
硬體支援3 : Swap 硬體指令 • 在執行完整個指令之前都不會被中斷。 • Swap指令會交換參數 a 與 b 兩個字元組的內容。 Void Swap (Boolean &a, Boolean &b) { Boolean temp = a; a = b; b = temp; } 19
避免競爭 2 : 號誌 Semaphore • 號誌是十分常用的同步工具,可以簡單地解決較為複雜的同步問題。 • 大部分的作業系統都已經實作了號誌,作為行程間同步的工具。 • 號誌包含一個數值,該值在初始化之後就只能經由 signal() 與 wait() 兩個不可被中斷的函式去存取。 • 當一個行程在存取號誌的值時,其他行程無法存取同一個號誌的值。 20
計數號誌的使用 (1) • 解決臨界區間的問題 do { wait(mutex); critical section signal(mutex); remainder section } 21
計數號誌的使用 (2) • 解決同步的問題 P2 P1 wait(mutex) S1; S2 signal(mutex) 22
計數號誌 – 忙碌等待版 • 計數號誌 Counting Semaphore void wait(s) { while (s<=0) ; s--; } void signal(s) { s++; } 23
計數號誌 – 非忙碌版 void wait(s) { S.value - -; if (S.value < 0) block() ; } void signal(S) { S.value++; if (S.value <=0) wakeup(p); } 24
二元號誌 • 利用硬體實作對二元號誌的支援,簡單又快。 • 利用二元號誌再實作計數號誌。 25
利用二元號誌實作計數號誌 void wait(S) { wait(S1) C--; if (C < 0) { signal(S1); wait(S2); } signal(S1); } void signal(s) { wait(S1); C++; if (C<=0) signal(S2); else signal(S1); } 26
生 產 者 消 耗 者 do { … 產生一個新的項目放在 nextp … wait(empty); wait(mutex); … 將 nextp 加入到緩衝區中 … signal(mutex); signal(full); } while(1); do { wait(full); wait(mutex); … 將一個項目由緩衝區中 移到 nextc … signal(mutex); signal(empty); … 消耗放在 nextc 中的項目 … } while(1); 以號誌實作生產者-消費者問題 • empty 初始化為 n full 初始化為 0 27
臨界區域 (Critical Region) • 一種高階的同步方法 • 語法 • region V when B do S; • V : shared T; • 注意 : 必須小心 B 的條件設定,條件相同者不能保證其執行的先後順序 • 例如: • R1 : region V when true do S1 • R2 : region V when true do S2 • 則 R1, R2 不一定誰會先被執行。 28
以臨界區域實作 : 生產者-消費者 Consumer Producer region buffer when (count < n) { pool[in] = nextp; in = (in + 1) % n; count ++; } region buffer when (count > 0) { nextc = pool[out]; out = (out + 1) % n; count --; } 29
監督程式 (Monitor) • 監督程式是另外一種高階的同步工具。 • 對較複雜的同步問題提供了更一般性的實作工具。 • 由一些變數宣告及函式所組成,變數的值定義了監督程式的狀態。 • 保證只有一個行程在監督程式中執行所定義的函式。 • 在監督程式中,程式設計師不需要撰寫有關同步的程式碼,但是可以利用條件變數來定義自己的同步機制。 • 哲學家晚餐問題可以利用監督程式來實作。 30
結語 • 行程不同步可能會產生競爭情況 • 為了避免競爭情況必需保護臨界區間 • 臨界區間的保護可用行程同步的方式 • Mutex • Semaphore 31
比較 - 臨界區域 v.s.臨界區間 • 請注意,臨界區域 (Critical Region) 與 臨界區間 (Critical Section) 不同 • 臨界區間 (Critical Section) • 指的是不可同時執行的該區間 32
競爭狀況的範例 - RaceCondition.java import java.util.Random; class RaceCondition { public static void main(String[] args) throws Exception { StepThread.test(); } } class StepThread extends Thread { public static void test() throws Exception { StepThread a = new StepThread("A", 1); StepThread b = new StepThread("B", -1); a.start(); b.start(); a.join(); b.join(); System.out.println("counter="+StepThread.counter); System.out.println("lines=\n"+StepThread.lines); } 33
競爭狀況的範例 - RaceCondition.java static StringBuffer lines = new StringBuffer(); static int counter=0; int step; String name; StepThread(String pName, int pStep) { step = pStep; name = pName; } int register; public void run() { for (int i=0; i<10; i++) { String line = name+" i="+i+" step="+step+"\t before:counter="+counter; // counter = counter + step; register = counter; register = register + step; counter = register; line += " after:counter="+counter; // Y[ synchronized hBLX counter | Race Condition lines.append(line+"\n"); // System.out.println(line); // } } } } 34