可重入互斥鎖(recursive mutex)
普通互斥鎖什麼都不記,只知道「鎖著」或「空著」。所以如果已經持鎖的那個執行緒想再上一次鎖——比方某函式拿了鎖,又呼叫一個同樣要拿這把鎖的輔助函式——它就會等一個只能由它自己給出的釋放。這就是自我死結。可重入互斥鎖是一把帶有記憶與計數的互斥鎖:它允許同一個執行緒多次上鎖而不凍結。
它的運作靠追蹤兩件事:目前是哪個執行緒擁有它,以及這個擁有者上了幾次鎖。某執行緒第一次上鎖時,設定擁有者並把計數設為一。同一執行緒每多上一次鎖,計數就加一;每解一次鎖就減一。只有當計數降回零時,這把鎖才算真正釋放、開放給其他執行緒。所以可重入互斥鎖必須解鎖的次數剛好等於上鎖的次數。另一個執行緒在它仍被擁有時想上鎖,依然會正常阻塞。
當一個上了鎖的函式可能透過好幾層呼叫,最終又重新進入會拿同一把鎖的程式碼時,可重入互斥鎖很方便——這在較舊或通用的函式庫程式碼中很常見。但有經驗的工程師多半把它當成一種壞味道,而非解法。需要重入通常意味著你臨界區間的邊界並不清楚,而可重入鎖很容易讓人搞不清楚在巢狀的鎖之間究竟有哪些不變式成立。更乾淨的設計往往是抽出一個私有、不上鎖的「核心」函式(它假設鎖已經被持有),再由兩個入口分別呼叫它。
用 PTHREAD_MUTEX_RECURSIVE 時,一個執行緒可以做 lock(); lock(); ...; unlock(); unlock(); ——內層的 lock 只是把計數從 1 加到 2,配對的內層 unlock 把它降回 1,所以這把鎖只有在外層 unlock 之後才真正空出來。
同一個擁有者、一個記錄巢狀持有次數的計數;只有計數回到零時鎖才釋放。
可重入互斥鎖只防止單一執行緒的自我死結;它對不同執行緒之間的死結毫無額外保護。會想用到它,通常代表設計該重構了,而不是一個值得享受的功能。