Guarded Suspension
January 10, 2022有時你會希望執行緒呼叫物件方法,而物件狀態尚未準備好,執行流程必須暫停,這應該不難,以 Java 為例,可以呼叫 wait
方法,讓執行緒暫停,進入等待集(wait set),問題在於誰負責檢查狀態、要求執行緒等待?
Semaphore
視情境而定,你可能決定由物件本身檢查自身狀態,若處於未準備好的狀態,要求執行緒等待,例如,有些場所會設置容流人數燈號,每進入一人就數字減一,離開一人數字加一,燈號物件負責檢查數字,若數字為 0 就不能進場,根據這個需求,來設計一個 Semaphore
:
class Semaphore {
private int value;
Semaphore(int value) {
this.value = value;
}
synchronized void acquire() {
while(value == 0) {
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
value--;
}
synchronized void release() {
value++;
if(value == 1) {
notifyAll();
}
}
}
建立 Semaphore
可指定燈號最大數值,每呼叫一次 acquire
,數值減一,在數值 0 時,若呼叫了acquire
,執行緒就會進入等待,每呼叫一次 release
,數值加一,如果這時數值為 1,通知等待中的執行緒。
來用這個 Semaphore
模擬一下客人的進場、離場:
static void pause(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
var MAX = 5;
var semaphore = new Semaphore(MAX);
var random = new Random();
for(var n = 0; n < 20; n++) {
pause(random.nextInt(0, 5) * 10);
var g = n;
new Thread(() -> {
while(true) {
semaphore.acquire();
System.out.println(g + "進場");
pause(random.nextInt(0, 10) * 100);
semaphore.release();
System.out.println(g + "離場");
}
}).start();
}
}
若程式語言環境提供執行緒功能,標準 API 可能就內建了 Semaphore
,例如 Java 就有 java.util.concurrent.Semaphore
。
Barrier
如果物件本身負責檢查狀態、要求執行緒等待,以上這類的實現概念,可以使用 Guarded Suspension 這個名稱來作為溝通,另一個常見案例是 Barrier。
Barrier 的意思是「柵欄」,也就是設定柵欄並指定數量,如果有執行緒先來到這個柵欄,必須等待其他執行緒也來到這個柵欄,直到指定的執行緒數量達到,全部執行緒才能繼續往下執行。
來個簡單的 Barrier
實作:
class Barrier {
private int value;
private int count;
Barrier(int value) {
this.value = value;
}
synchronized void await() {
count++;
while(count < value) {
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
notifyAll();
}
}
一個應用場景是,若希望伺服端執行緒與客戶端執行緒都必須準備就緒,才能繼續往下執行的話,可以如下:
var MAX = 2;
var barrier = new Barrier(MAX);
var server = new Thread(() -> {
barrier.await();
System.out.println("Server go!");
});
var client = new Thread(() -> {
barrier.await();
System.out.println("Client go!");
});
server.start();
client.start();
就 Java 標準 API 而言,是有個 java.util.concurrent.CyclicBarrier
可以直接使用;基本上,多執行緒環境的資源控管,不建議自己搞,畢竟考量的因素很多,以上只是簡單示範罷了,認識多執行緒模式,主要是可從中認識一些高階 API 的原理,並瞭解其適用的場景。