1 我們先了解CPU緩存
CPU緩存為了解決CPU運算速度與記憶體讀寫速度不比對的問題,因為CPU運算速度要比記憶體讀寫速度快得多
一次主記憶體的通路通常在幾十到幾百個時鐘周期
一次L1高速緩存的讀寫隻需要1~2個時鐘周期
一次L2高速緩存的讀寫也隻需要數十個時鐘周期
CPU大多數情況下讀寫都不會直接通路記憶體,取而代之的是CPU緩存,CPU緩存是位于CPU與記憶體之間的臨時存儲器(簡單了解為寄存器),它容量比記憶體小得多但是交換速度卻比記憶體快得多。而緩存中的資料是記憶體中的一小部分資料,但這一小部分是短時間内CPU即将通路的,當CPU調用大量資料時,就可先從緩存中讀取,進而加快讀取速度
CPU緩存可分為:一級緩存(是與CPU結合最為緊密的CPU緩存)、二級緩存、三級緩存,每一級緩存中所存儲的資料全部都是下一級緩存中的一部分
當CPU要讀取資料時,首先從一級緩存中查找,如果沒有再從二級緩存中查找,如果還是沒有再從三級緩存中或記憶體中查找。一般來說每級緩存的命中率大概都有80%左右,隻剩下20%的總資料量才需要從二級緩存、三級緩存或記憶體中讀取。
CPU執行計算的過程如下:
程式以及資料被加載到主記憶體
指令和資料被加載到CPU緩存
CPU執行指令,把結果寫到高速緩存
高速緩存中的資料寫回主記憶體
2 總線鎖
每個CPU都有一級緩存,但是,我們卻無法保證每個CPU的一級緩存資料都是一樣的,如何保證各個CPU緩存中的資料是一緻的。就是CPU的緩存一緻性問題
1)總線鎖
一種處理一緻性問題的辦法是使用Bus Locking(總線鎖)。當一個CPU對其緩存中的資料進行操作的時候,往總線中發送一個Lock信号。 這個時候,所有CPU收到這個信号之後就不操作自己緩存中的對應資料了,也就是把資料直接寫入主記憶體,當操作結束,釋放鎖以後,所有的CPU就去記憶體中擷取最新資料更新。
3 volatile如何保證可見性
我們把有volatile修飾的變量編譯成部分彙編,這裡有個lock指令
0x01a3de24: lock addl $0X0,(%esp);
如果是寫操作,cpu會發出一個lock指令,CUP會把資料直接寫到到主記憶體
如果是讀操作,cpu會發出一個unlock指令, 所有的CPU就去記憶體中擷取最新資料更新
4 volatile如何保證指令重排序
現代的作業系統都是多處理器.而每一個處理器都有自己的緩存,并且這些緩存并不是實時都與記憶體發生資訊交換.這樣就可能出現一個cpu上的緩存資料與另一個cpu上的緩存資料不一緻的問題.而這樣在多線程開發中,就有可能導緻出現一些異常行為.
而作業系統底層為了這些問題,提供了一些記憶體屏障用以解決這樣的問題.目前有4種屏障.
LoadLoad屏障:對于這樣的語句Load1; LoadLoad; Load2,在Load2及後續讀取操作要讀取的資料被通路前,保證Load1要讀取的資料被讀取完畢。
StoreStore屏障:對于這樣的語句Store1; StoreStore; Store2,在Store2及後續寫入操作執行前,保證Store1的寫入操作對其它處理器可見。
LoadStore屏障:對于這樣的語句Load1; LoadStore; Store2,在Store2及後續寫入操作被刷出前,保證Load1要讀取的資料被讀取完畢。
StoreLoad屏障:對于這樣的語句Store1; StoreLoad; Load2,在Load2及後續所有讀取操作執行前,保證Store1的寫入對所有處理器可見。
在每個volatile寫操作前插入StoreStore屏障,在寫操作後插入StoreLoad屏障;
在每個volatile讀操作前插入LoadLoad屏障,在讀操作後插入LoadStore屏障;
由于記憶體屏障的作用,避免了volatile變量和其它指令重排序
參考連結:
https://crowhawk.github.io/2018/02/10/volatile/ https://www.jianshu.com/p/ef8de88b1343 https://my.oschina.net/LucasZhu/blog/1537330