行程同步與臨界區間問題

號誌(semaphore)

/ SEM-uh-for /

號誌由戴克斯特拉發明,是一個圍繞「單一整數計數器加上兩個原子操作」打造的同步工具。經典的畫面是餐廳的一盤呼叫器:計數就是盤子上還剩幾個呼叫器。一桌客人來了就拿走一個(計數下降);若盤子空了,他們就得等到有人還回來。一桌吃完就放回一個呼叫器(計數上升),於是某桌等待的客人就能拿走。號誌管理的,正是這套「某樣東西有幾個可用、沒得用時誰來等」的記帳。

它提供兩個操作,歷史上叫 P 和 V(荷蘭文,源自戴克斯特拉),今日通常稱為 wait 和 signal。wait(S) 把計數減一;若計數會變成負的,呼叫的執行緒就阻塞(睡著),直到計數再次為正。signal(S) 把計數加一;若有任何執行緒在等,就喚醒其中一條讓它前進。兩個操作都以原子方式進行,所以計數絕不會被兩條執行緒同時弄壞。關鍵在於,一個造得好的號誌並不忙碌等待:wait 把被阻塞的執行緒放進一個等待佇列並讓出 CPU,signal 則把一條執行緒移出那個佇列——不自旋,計數與佇列都由作業系統管理。

號誌做兩件看似不同、其實是同一機制的工作。作為一個初始化為 N 的計數號誌,它一次最多放 N 條執行緒通過——非常適合管理 N 個相同的資源(N 條資料庫連線、N 個空閒的緩衝槽)。初始化為 1 時,它就充當一把鎖,提供互斥(二元號誌)。而若不對稱地使用——一條執行緒只 wait、另一條只 signal——它就成了在執行緒間傳遞事件的方式,例如「資料現在準備好了」。關鍵的誠實之處:號誌本身保護不了任何東西。它只有在每一條碰觸共用資料的執行緒都以正確順序正確地呼叫 wait 與 signal 時才有效;少一個 wait、一個沒有對應 wait 的 signal、或把 wait 與 signal 弄反,你就會得到競爭、死結,或丟失喚醒。它的威力,完全來自有紀律、一致的使用。

一個有 3 台印表機的資源池:把計數號誌初始化為 S = 3。每個工作做 wait(S)(取走一台印表機;第 4 個工作會阻塞到有一台空出來),列印,再 signal(S)(歸還它,喚醒一個等待者)。若改成 S = 1,同一段程式碼就變成圍著單一資源的一把普通互斥鎖。

wait(S) 取走一個(可能阻塞);signal(S) 歸還一個(可能喚醒等待者)。計數 = 還剩幾個可用。

號誌本身不強制任何事——只有在每條執行緒都正確使用 wait/signal 時才有效。一次疏忽的存取、少一個 wait、或把 wait/signal 對調,都會悄悄讓它失效。

又称
counting semaphore信號量旗號