官术网_书友最值得收藏!

3.4 指令重排序導致的可見性問題

什么是指令重排序呢?為了更加直觀地理解,筆者還是通過一個案例來說明。

上面這段程序的邏輯如下:

? 定義四個int類型的變量,初始化都為0。

? 定義兩個線程t1、t2,t1線程修改a和x的值,t2線程修改b和y的值,分別啟動兩個線程。

? 正常情況下,x和y的值,會根據t1和t2線程的執行情況來決定。

○ 如果t1線程優先執行,那么得到的結果是x=0、y=1。

○ 如果t2線程優先執行,那么得到的結果是x=1、y=0。

○ 如果t1和t2線程同時執行,那么得到的結果是x=1、y=1。

我們來看一下運行結果:

讀者看到這個結果是不是大吃一驚?在運行了13萬次之后,竟然得到一個x=0、y=0的結果。

其實這就是所謂的指令重排序問題,假設上面的代碼通過指令重排序之后,變成下面這種結構:

經過重排序之后,如果t1和t2線程同時運行,就會得到x=0、y=0的結果,這個結果從人的視角來看,就有點類似于t1線程中a=1的修改結果對t2線程不可見,同樣t2線程中b=1的執行結果對t1線程不可見。

3.4.1 什么是指令重排序

指令重排序是指編譯器或CPU為了優化程序的執行性能而對指令進行重新排序的一種手段,重排序會帶來可見性問題,所以在多線程開發中必須要關注并規避重排序。

從源代碼到最終運行的指令,會經過如下兩個階段的重排序。

第一階段,編譯器重排序,就是在編譯過程中,編譯器根據上下文分析對指令進行重排序,目的是減少CPU和內存的交互,重排序之后盡可能保證CPU從寄存器或緩存行中讀取數據。在前面分析JIT優化中提到的循環表達式外提(Loop Expression Hoisting)就是編譯器層面的重排序,從CPU層面來說,避免了處理器每次都去內存中加載stop,減少了處理器和內存的交互開銷。

第二階段,處理器重排序,處理器重排序分為兩個部分。

? 并行指令集重排序,這是處理器優化的一種,處理器可以改變指令的執行順序。

? 內存系統重排序,這是處理器引入Store Buffer緩沖區延時寫入產生的指令執行順序不一致的問題,在后續內容中會詳細說明。

為了幫助讀者理解,筆者專門針對并行指令集的原理做一個簡單的說明。

什么是并行指令集?在處理器內核中一般會有多個執行單元,比如算術邏輯單元、位移單元等。在引入并行指令集之前,CPU在每個時鐘周期內只能執行單條指令,也就是說只有一個執行單元在工作,其他執行單元處于空閑狀態;在引入并行指令集之后,CPU在一個時鐘周期內可以同時分配多條指令在不同的執行單元中執行。

那么什么是并行指令集的重排序呢?如圖3-12所示,假設某一段程序有多條指令,不同指令的執行實現也不同。對于一條從內存中讀取數據的指令,CPU的某個執行單元在執行這條指令并等到返回結果之前,按照CPU的執行速度來說它足夠處理幾百條其他指令,而CPU為了提高執行效率,會根據單元電路的空閑狀態和指令能否提前執行的情況進行分析,把那些指令地址順序靠后的指令提前到讀取內存指令之前完成。

實際上,這種優化的本質是通過提前執行其他可執行指令來填補CPU的時間空隙,然后在結束時重新排序運算結果,從而實現指令順序執行的運行結果。

圖3-12 并行指令集重排序

3.4.2 as-if-serial語義

as-if-serial表示所有的程序指令都可以因為優化而被重排序,但是在優化的過程中必須要保證是在單線程環境下,重排序之后的運行結果和程序代碼本身預期的執行結果一致,Java編譯器、CPU指令重排序都需要保證在單線程環境下的as-if-serial語義是正確的。

可能有些讀者會有疑惑,既然能夠保證在單線程環境下的順序性,那為什么還會存在指令重排序呢?在JSR-133規范中,原文是這么說的。

The compiler, runtime, and hardware are supposed to conspire to create the illusion of as-if-serial semantics, which means that in a single-threaded program, the program should not be able to observe the effects of reorderings. However, reorderings can come into play in incorrectly synchronized multithreaded programs, where one thread is able to observe the effects of other threads, and may be able to detect that variable accesses become visible to other threads in a different order than executed or specified in the program.

as-if-serial語義允許重排序,CPU層面的指令優化依然存在。在單線程中,這些優化并不會影響整體的執行結果,在多線程中,重排序會帶來可見性問題。

另外,為了保證as-if-serial語義是正確的,編譯器和處理器不會對存在依賴關系的操作進行指令重排序,因為這樣會影響程序的執行結果。我們來看下面這段代碼。

上述代碼按照正常的執行順序應該是1、2、3,在多線程環境下,可能會出現2、1、3這樣的執行順序,但是一定不會出現3、2、1這樣的順序,因為3與1和2存在數據依賴關系,一旦重排序,就無法保證as-if-serial語義是正確的。

至此,相信讀者對指令重排序導致的可見性問題有了一個基本的了解,但是在CPU層面還存在內存系統重排序問題,內存系統重排序也會導致可見性問題,下面筆者圍繞這個問題做一個詳細的分析。

主站蜘蛛池模板: 新巴尔虎右旗| 依安县| 六盘水市| 泉州市| 甘南县| 和顺县| 山西省| 松江区| 定兴县| 竹北市| 扎赉特旗| 灵宝市| 棋牌| 喜德县| 屯昌县| 永修县| 抚顺市| 合肥市| 鄂伦春自治旗| 额济纳旗| 长宁县| 名山县| 潞城市| 十堰市| 阿拉善右旗| 龙井市| 赤城县| 青川县| 静海县| 扶绥县| 石楼县| 乐昌市| 清水河县| 永胜县| 深泽县| 牡丹江市| 云霄县| 都安| 西华县| 德格县| 板桥市|