synchronizes-with 關係(the synchronizes-with relation)
如果 happens-before 是鏈條,那麼 synchronizes-with 就是把兩個執行緒的鏈條接在一起的那一道焊點。它是整個模型中「唯一」的跨執行緒邊——保證從一個執行緒越界進入另一個執行緒的那個精確時刻。其餘一切(sequenced-before)都活在單一執行緒之內;synchronizes-with 是那座橋。
建立它最典型的方式是 release/acquire 配對:一個原子變數上的 release 操作 synchronizes-with「同一個」變數上的 acquire 操作——前提是該 acquire 真的讀到了那個 release 所寫的值(或該變數 release 序列中之後的某個值)。當這次讀取發生時,一條跨執行緒邊就喀地接上,再把它黏在兩個執行緒各自的 sequenced-before 順序之間,你就得到一條把可見性帶過去的 happens-before 路徑。其他操作也會 synchronizes-with:解開一個互斥鎖 synchronizes-with 下一次對它的上鎖;一個執行緒的結束 synchronizes-with 對它成功的 join();啟動一個執行緒 synchronizes-with 該執行緒主體的開始。
兩個最常見的臭蟲都圍繞著「讀到那個值」這個條件。若 acquire 讀到的是與 release 所存「不同」的值——比方一個陳舊值,或某個第三執行緒寫的值——就不會形成 synchronizes-with 邊,你也就沒有 happens-before,於是是一場資料競爭。而一個哪裡都沒有 acquire 配對的 release(或沒有 release 的 acquire)是惰性的:它什麼也不同步。同步是在共享原子上的雙方握手,不是你能只往一邊灑的性質。
store(&ready, 1, release) synchronizes-with load(&ready, acquire) 「只有當」載入回傳 1 時才成立。 若載入剛好回傳 0(它跑得太早),就不形成邊——所以對 acquire 做重試迴圈,通常才是你真正要的。
唯有當 acquire 讀到被釋放的值時,這條邊才存在;讀到舊值什麼也不同步。
synchronizes-with 要求 acquire 必須「觀察到」被釋放的值——把標籤配對是必要但不充分的;若載入讀到陳舊值,同步根本不會發生。