每日一個知識點:Volatile 和 CAS 的弊端之總線風暴

  • 2020 年 9 月 28 日
  • 筆記

每日一個知識點系列的目的是針對某一個知識點進行概括性總結,可在一分鐘內完成知識點的閱讀理解,此處不涉及詳細的原理性解讀。

一、什麼是總線風暴

總線風暴,聽着真是一個帥氣的詞語,但如果發生在你的系統上那就不是很美麗了,廢話不多說,先看圖說結論。

img

什麼是總線風暴,先來看結論

在java中使用unsafe實現cas,而其底層由cpp調用彙編指令實現的,如果是多核cpu是使用lock cmpxchg指令,單核cpu 使用compxch指令。如果在短時間內產生大量的cas操作在加上 volatile的嗅探機制則會不斷地佔用總線帶寬,導致總線流量激增,就會產生總線風暴。 總之,就是因為volatile 和CAS 的操作導致BUS總線緩存一致性流量激增所造成的影響。

img

二、一些需要的基礎知識

這裡有些基礎需要鋪墊下,了解過volatile和cas 的朋友都知道由於一個變量在多個高速緩存中都存在,但由於高速緩存間的數據是不共享的,所以勢必會有數據不一致的問題,為了解決這種問題處理器是通過總線鎖定緩存鎖定這兩個機制來保證複雜內存操作的原子性的。

img

1、總線鎖

在早期處理器提供一個 LOCK# 信號,CPU1在操作共享變量的時候會預先對總線加鎖,此時CPU2就不能通過總線來讀取內存中的數據了,但這無疑會大大降低CPU的執行效率。

2、緩存一致性協議

由於總線鎖的效率太低所以就出現了緩存一致性協議,Intel 的MESI協議就是其中一個佼佼者。MESI協議保證了每個緩存變量中使用的共享變量的副本都是一致的。

3、MESI 的核心思想

modified(修改)、exclusive(互斥)、share(共享)、invalid(無效)

如上圖,CPU1使用共享數據時會先數據拷貝到CPU1緩存中,然後置為獨佔狀態(E),這時CPU2也使用了共享數據,也會拷貝也到CPU2緩存中。通過總線嗅探機制,當該CPU1監聽總線中其他CPU對內存進行操作,此時共享變量在CPU1和CPU2兩個緩存中的狀態會被標記為共享狀態(S);

若CPU1將變量通過緩存回寫到主存中,需要先鎖住緩存行,此時狀態切換為(M),向總線發消息告訴其他在嗅探的CPU該變量已經被CPU1改變並回寫到主存中。接收到消息的其他CPU會將共享變量狀態從(S)改成無效狀態(I),緩存行失效。若其他CPU需要再次操作共享變量則需要重新從內存讀取。

緩存一致性協議失效的情況:

  • 共享變量大於緩存行大小,MESI無法進行緩存行加鎖;
  • CPU並不支持緩存一致性協議

4、嗅探機制

每個處理器會通過嗅探器來監控總線上的數據來檢查自己緩存內的數據是否過期,如果發現自己緩存行對應的地址被修改了,就會將此緩存行置為無效。當處理器對此數據進行操作時,就會重新從主內存中讀取數據到緩存行。

5、緩存一致性流量

通過前面都知道了緩存一致性協議,比如MESI會觸發嗅探器進行數據傳播。當有大量的volatile 和cas 進行數據修改的時候就會產大量嗅探消息。

三、總結性言論

通過上面一頓巴拉,大家應該對開局圖有一定的了解了,也大概知道了總線風暴的原因。這裡再做一下概括性的總結(當前內部還有很有詳細的機制,大家感興趣可以擼一波)

在多核處理器架構上,所有的處理器是共用一條總線的,都是靠此總線來和主內存進行數據交互。當主內存的數據同時存在於多個處理的高速緩存中時,某一處理器更新了此共享數據後。會通過總線觸發嗅探機制來通知其他處理器將自己高速緩存內的共享數據置為無效,在下次使用時重新從主內存加載最新數據。而這種通過總線來進行通信則稱之為」緩存一致性流量「。

因為總線是固定的,所有相應可以接受的通信能力也就是固定的了,如果緩存一致性流量突然激增,必然會使總線的處理能力受到影響。而恰好CAS和volatile 會導致緩存一致性流量增大。如果很多線程都共享一個變量,當共享變量進行CAS等數據變更時,就有可能產生總線風暴。

img

往期推薦

每日一個知識點系列:volatile的可見性原理

(最新 9000字) Spring Boot 配置特性解析

何時用多線程?多線程需要加鎖嗎?線程數多少最合理?

Spring Boot 知識清單(一)SpringApplication

高並發系統,你需要知道的指標(RT…)

img