從硬件級別再看可見性和有序性

 

前言

王子之前的文章對於併發編程中的可見性問題已經有了一個初步的介紹,總結出來就是CPU的緩存會導致可見性問題

這樣的解釋其實是沒有問題的,但這裏說的“緩存”其實一個籠統的概念,緩存其實指的是寄存器、高速緩存和寫緩衝器

今天我們就從硬件的級別再來探索一下出現可見性問題的原因,讓小夥伴們有一個更深的認識。

同時再深入探索一下有序性問題的產生原因。

如果小夥伴們對於寄存器、高速緩存、緩衝器、總線的概念還不清楚,建議自行去查閱資料瞭解。

 

出現可見性問題的原因

首先,我們知道每個CPU都有自己的寄存器,它用於存儲臨時的二進制數據,做一些數據運算。

所以當多個CPU各自運行一個線程的時候,就會導致在寄存器中對數據的修改對其他CPU是不可見的。

然後,一個CPU對變量的寫操作都是針對寫緩衝器的,並不是直接把值寫到主內存中。

所以沒有寫到主內存中的數據,對其他CPU是不可見的。

然後寫入緩衝器後,會把更新後的數據寫入到高速緩存中,之後把變量更新信息通過總線通知給其他CPU,但是其他CPU可能會認爲這個更新是無效的,不會更新它自己的高速緩存數據,這就導致了高速緩存的可見性問題。

整體的內存模型如圖:

 

MESI協議解決可見性

解決可見性問題的一種方案就是MESI協議,這個MESI協議,根據不同的硬件系統,會有不同的實現方式。

比如MESI的一種實現方式,就是CPU接收到變量更新的消息後,直接更新數據到自己的高速緩存中,這樣各個CPU高速緩存中的數據就一致了,解決了可見性問題。

說到MESI協議,王子要跟大家說兩個新名詞,flush和refresh。

先來說一下flush。

flush就是把自己更新的值刷新到高速緩存(或主內存)中,除了flush操作,同時還會發送一個消息到總線(bus),通知其他處理器某個變量值被修改了。

那refresh又是什麼呢?

refresh指的是,處理器中的線程在讀取某個變量的時候,如果發現其他處理器的線程修改了這個變量,那就必須過期掉自己高速緩存中的值,從其他處理器的高速緩存(或主內存)中讀取變量,同步到自己的高速緩存中。

這就是MESI協議最最基礎的原理。

 

探索有序性問題

之前的文章我們已經說過,指令重排會導致有序性問題,那麼具體什麼時候會發生指令重排呢?這就要從代碼的編譯過程說起了。

首先我們寫的java代碼會被javac靜態編譯器進行編譯,編譯成class字節碼,然後會經過JIT動態編譯器編譯成操作系統可以執行的機器碼,在編譯的過程中,有一個編譯優化的概念,爲了提高執行效率,可能會發生指令重排,例子就是之前文章中我們說到的double check單例模式,這裏就不再說明了。

除了編譯會發生指令重排,CPU本身也可能改變指令的執行順序,另外高速緩存、寫緩衝器和無效隊列在硬件層面也可能會改變指令的順序。

 

接着我們來探索一下CPU是如何出現指令重排的?這就涉及到CPU的指令亂序和猜測執行機制了。

首先我們來看一下指令亂序機制。

CPU獲取到的指令是不一定能直接執行的,比如指令要執行網絡通信、磁盤IO、獲取鎖等,爲了提升效率,CPU使用的就是指令亂序機制。

把編譯好的指令一條一條的讀取到處理器中,但哪條指令先就緒可以執行了,就會先執行,而不會去按照順序執行。

然後將指令執行後的結果放入指令重排序處理器中,重排序處理器再把這些結果按照最開始的指令順序同步到主內存或寫緩衝器中。

 

這就是指令亂序機制,可能出現有序性問題。

除此之外還有一個猜測執行機制,比如if判斷後,執行一堆代碼,可能先去執行這堆代碼,然後再進行判斷,如果判斷成立,就採納執行的結果,否則不採納執行的結果,這種機制也可能出現有序性問題。

說完了cpu,再來看看高速緩存、寫緩衝器是如何導致內存重排序的

首先來了解兩個概念,storeload

store指的是處理器將數據寫入寫緩衝器這一過程,load指的是處理器從高速緩存裏讀數據這一過程。這兩個過程可能導致內存重排序,一共有四種可能:

LoadLoad重排序:一個處理器先執行L1,後執行L2,另一個處理器可能看到的是先執行L2,後執行L1;

StoreStore重排序:一個處理器先執行W1,後執行W2,另一個處理器可能看到的是先執行W2,後執行W1;

LoadStore重排序:一個處理器先執行L1,後執行W2,另一個處理器可能看到的是先執行W2,後執行L1;

StoreLoad重排序:一個處理器先執行W1,後執行L2,另一個處理器可能看到的是先執行L2,後執行W1;

爲了便於理解,以StoreStore重排序爲例,假如高速緩存按照W1W2的順序接收到兩個操作,爲了提高性能,先執行了W2,後執行了W1,這就發生了內存重排序,其他處理器看到的順序就是重新排序後的順序。

 

總結

今天我們從硬件級別重新認識了一下可見性和有序性問題。

對於可見性,我們介紹了CPU的緩存機制和MESI協議。

對於有序性,我們介紹了編譯優化、CPU的指令亂序和猜測執行、內存重排序機制導致的指令重排問題。

這就是全部內容了,希望小夥伴們能夠通過對底層原理的理解,更容易的理解併發編程。

 

往期文章推薦:

JVM專欄

消息中間件專欄

併發編程專欄

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章