互斥鎖(mutex)
/ MYOO-teks /
互斥鎖就是讓互斥成真的那把日常用鎖。它的英文 mutex 是 mutual exclusion(互斥)的縮寫。把它想成掛在掛勾上的唯一一把鑰匙:碰共享資料前先把鑰匙拿走(上鎖),做完工作後再掛回去(解鎖)。只要鑰匙在你手上,別的執行緒就拿不到——它們得在掛勾旁等。鑰匙只有一把,所以一次剛好只有一個執行緒能進入受保護的區域。
互斥鎖有兩個操作,通常叫上鎖(lock)與解鎖(unlock)(也說成取得 acquire 與釋放 release)。呼叫 pthread_mutex_lock() 若鎖是空的就立即成功,否則就阻塞呼叫端的執行緒——把它送去睡——直到持鎖者呼叫 pthread_mutex_unlock()。當一個執行緒成功上了某把鎖、還沒解鎖時,我們說它正持有這把鎖;持鎖期間,它是唯一獲准進入臨界區間的執行緒。樣式永遠是上鎖、一小段臨界區間、再解鎖——而關鍵在於,每一條離開的路徑都必須解鎖,包括提早 return 與錯誤路徑,否則別的執行緒會永遠等下去。
互斥鎖是並行程式碼的主力:保護任何共享可變狀態的預設工具。有兩點要當心。普通的互斥鎖不可重入——如果已經持鎖的那個執行緒再呼叫一次 lock,它會跟自己死結(真的需要重入時請用可重入互斥鎖)。而且互斥鎖只保護「你決定讓它保護」的那份資料:鎖與它所守護的資料之間的關聯,只存在於你的腦中與你的註解裡,從不存在於編譯器中。只要你在某一處忘了上鎖,互斥鎖就救不了你。
pthread_mutex_lock(&m); balance = balance + amount; pthread_mutex_unlock(&m); ——這筆存款完全在鎖內完成,所以兩個執行緒同時存款也不會互相蓋掉對方的更新。
上鎖、做最小的臨界區間、在每條離開路徑上解鎖。
只有上鎖的那個執行緒才能解開一把普通互斥鎖——互斥鎖是有「擁有者」的,這點和任何執行緒都能 post 的號誌不同。持鎖時間超過必要,或在持鎖時做慢工(檔案 I/O、阻塞呼叫),會把你整個程式串成單線,悄悄毀掉效能。