並發與鎖
JUC AQS
AQS整體結構
鎖的擷取過程
擷取失敗掛起的過程
釋放鎖喚醒等待的過程
如何防止丟失喚醒
入隊時不能“貪睡”,找可靠等待者並讓他叫醒自己,不在進而就擷取鎖,其他所有情況都告訴標記前驅別忘了叫醒自己
這就夠了嗎,顯然還不行;滿足自己去“睡”的條件的check和“睡”顯然不是一個原子操作。在check後和“睡著”前如果條件變了就沒人叫醒
unpark和park早就預防相關問題,猜想內部是有變數記憶了上次的操作後狀態,同時基於作業系統提供的鎖保證了原子性
如何防止驚群問題
鎖的喚醒通常是有喚醒一個等待線程和喚醒全部等待線程的;通常在編程條件下如果想減少全部喚醒的引發不必要的競爭時,還要注意虛假
喚醒問題,被喚醒的線程如果條件不滿足條件釋放鎖後造成喚醒鏈脫節的問題。而這種情況又不能簡單在自己釋放鎖前去喚醒其他等待者。
所以比如在leader-follower模式中,如果leader線程只在原leader線程離任時喚醒任意一個等待線程時,則當線程都在follower模式
時因為沒有喚醒者陷入停機狀態,所以在每次完成工作時還要嘗試自薦為leader。如果leader在離任時喚起其他所有線程顯然時驚群的。
AQS小結
從CAS到記憶體屏障再到緩衝一致性協議
volatile happens-before
如果a線程先寫入新值,b線程緊接著(賦值後隔一個刻度,這樣可以使得無論是lock首碼的指令排空store buffer
並且失效其他處理器的對於緩衝得以執行完,或者時鐘中斷引發store buffer排空進而觸發緩衝一致性同步)去讀就能
最新值,不過由於需要間隔一個刻度,看似違反happens-before;不過由於緊接著是lock的情況在緊接著的時鐘
周期裡由於lock觸發緩衝一致性同步會失效該資料項目的舊值所以不會有問題,時鐘中斷的間接觸發緩衝同步也類似。
https://github.com/dmlloyd/openjdk/blob/jdk/jdk/src/hotspot/share/prims/unsafe.cpp
- 編譯器最佳化、cpu亂序執行【關於體繫結構只針對X86-64其他亂序自由度更大的暫不考慮,料想其原理是類似的】
冒險:
資料冒險
結構冒險
控制冒險(分支冒險)
MESI、MOSI、MESIF
一圖勝過千言(待添加),
硬體結構: 寫緩衝的讀取優先與L1cache,寫合并WC最佳化;寫回緩衝WB,寫穿透緩衝WT,
寫失效WI,經驗表明寫失效,更節約頻寬
寫更新WU,看起來快,有時候是不必要的如果更新每個緩衝比起一條失效指令代價大許多;
更新可能是斷續多次而失效只要一次,而且緩衝行內容的大小比緩衝地址大位寬大不少
X86-64一致性高,LoadLoad Memory Barrier是因為讀屏障會強制等待Invalidate Queue清空才繼續執行,
這樣可以消除其引發的讀亂序問題
緩衝一致性協議顯然在硬體上有鎖的語義的
loadload
loadstore
storeload (x86 TSO 唯一亂序的地方)
storestore
Synchronized
Futex
Futex是一種使用者態和核心態混合的同步機制。首先,同步的進程間通過mmap共用一段記憶體,futex變數就位於這段共用 的記憶體中且操作是原子的,當進程嘗試進入互斥區或者退出互斥區的時候,先去查看共用記憶體中的futex變數,如果沒有競爭發生,則只修改futex,而不 用再執行系統調用了。當通過訪問futex變數告訴進程有競爭發生,則還是得執行系統調用去完成相應的處理(wait 或者 wake up)。簡單的說,futex就是通過在使用者態的檢查,(motivation)如果瞭解到沒有競爭就不用陷入核心了,大大提高了low-contention時候的效率。 Linux從2.5.7開始支援Futex。
自旋鎖
核心的基本鎖之一
自旋最佳化(jdk9與自旋提示)
自旋是在迴圈中不斷重試檢測條件變數的值,這樣為了保證可見度,就需要不斷的同步緩衝,造成很多不必要的開銷。
鎖的構成要素
>cas>等待隊列>屏蔽中斷禁止搶佔的原子調度內容相關的切換
理解與體會
不總結不準備就會拔劍四顧心茫然
讀寫時序
控制流程與資料流的等價性
- 幾種並行存取模型 actor、CSP、CPS/CallCC
- 鎖的本質問題是時序問題,調度可以解決時序問題,調度問題從cpu角度看又是執行權的問題,鎖是保護了資料,鎖是通過限制代碼的執行來保護資料的。
mesi為什麼可以保證一致性對分布式環境緩衝一致性有什麼啟示
首先匯流排的作用是巨大的,他保證了多核乃至多路cpu間的緩衝一致性
分布式環境下由於沒有統一的匯流排,缺少收斂的協調控制。