臨界區間(critical section)
臨界區間是一段會碰到共享可變狀態、因此不可被兩條執行緒同時執行的程式碼。把它想成一間單人電話亭,有門:你在裡面講電話時,誰也不能進來;他們在外面等你出來。那間電話亭就是臨界區間,而「一次一人」的規則正是讓共享資源不被攪亂的關鍵。辨認出你程式碼中哪些段落是臨界區間,是讓並行程式正確的第一個實用步驟。
更精確地說:臨界區間是一段程式碼,從第一條讀或寫某筆共享資料的指令,到最後一條這麼做的指令,在這期間那筆共享資料可能處於不一致、改了一半的狀態。當一條執行緒正做到這種區段的中途——比方說它已讀了一個值、算出一個新值、但還沒存回去——資料就暫時是錯的,而此時闖進來的另一條執行緒會看到、或踩爛那團亂。於是任務就是:確保同一時間最多只有一條執行緒在臨界區間內,這樣每條執行緒都會在下一條開始前完成整個更新。這個保證叫做互斥(mutual exclusion),而提供它的工具(互斥鎖等)是 Field n 的主題。
為臨界區間命名為何有助於思考:它把整個問題聚焦。你不需要讓整個程式都執行緒安全;你需要找出那些碰共享狀態的特定區域,只保護那些就好。讓臨界區間小——保護只持續到剛好涵蓋整個不一致窗口的最短一段——因為臨界區間內的執行緒會擋住別人,所以不必要地大的臨界區間會掐住你的並行度。並且確保對某筆共享資料的「每一次」存取都被涵蓋:一個保護了寫入卻把某次讀取留在外面的臨界區間,仍會在那次讀取上競爭。
counter++ 的三個步驟——讀 counter、加一、寫回——構成一個臨界區間。把它們包起來,讓一次只有一條執行緒去跑(例如夾在 pthread_mutex_lock() 與 pthread_mutex_unlock() 之間),就讓整個更新成為一個不可分割的步驟。
臨界區間就是必須以互斥方式執行的程式碼——一次一條執行緒。
保護了一個臨界區間,卻忘了另一個碰同筆資料的,仍留有競爭——對某筆共享項目的每一次存取都必須用同一份保護。並且讓區間保持小:過大的臨界區間會把本可平行的工作串行化。