同步

監視器(monitor)

鎖與條件變數很強大,卻容易誤用——忘了解鎖、拿錯鎖、不持鎖就 signal。監視器是把它們綑在一起的更整潔的封裝。想法是:拿一個保存共享資料的物件,給它附上一把鎖,並訂下規則:每個公開方法在進入時自動取得那把鎖、離開時自動釋放。從外面看,這個物件就是執行緒安全的;上鎖是內建的,而非由每個呼叫端自己外掛。

具體說,監視器結合三樣東西:共享資料、一把保護它全部的互斥鎖,以及一或多個讓執行緒等待狀態變化的條件變數。一次只有一個執行緒能「在監視器裡面」——執行它任何一個方法——這讓你免費獲得整個物件範圍的互斥。當某方法需要等待某條件時,它對條件變數呼叫 wait,這會釋放監視器的鎖、讓另一個執行緒進來;當另一個方法改變狀態時就呼叫 signal 或 broadcast。Java 直接內建了這個:每個物件都有一把隱含的鎖,synchronized 關鍵字加上 wait/notify 就把它變成一個監視器。在 C 裡,你得親手用一個 pthread_mutex_t 與幾個放在 struct 裡的 pthread_cond_t 拼出監視器,並自律地在每個方法頂端上鎖、底端解鎖。

監視器重要,是因為它把上鎖紀律從「每個呼叫端都要記得」搬到「物件自己保證」,消除了一整類「忘了上鎖」的錯誤。它是 Java 的 synchronized、C# 的 lock,以及一般「執行緒安全物件」樣式的概念祖先。誠實的侷限:監視器只保護它擁有的資料,而橫跨兩個監視器的操作,若不同執行緒以不同順序進入它們,仍然可能死結——把鎖綑起來並不會廢除鎖排序的需要。

一個計數器的 C 版「監視器」:struct { pthread_mutex_t m; long value; } C; ——每個操作都做 pthread_mutex_lock(&C.m); ... C.value++; ... pthread_mutex_unlock(&C.m);,於是鎖跟著資料走,而不是跟著每個呼叫端走。

把資料、它的鎖與它的條件變數綑在一起;每個方法在進入時上鎖。

C 沒有內建的監視器——「每個方法都上鎖」這個保證是你靠慣例強制的,只要有一個方法忘了就破功。而在監視器內部(持著它的鎖)呼叫到不明的程式碼,若那段碼重入或又拿了別的鎖,就會招來死結。

又稱
monitor objectsynchronized object管程監控物件