高併發程式設計 重排序

2021-09-29 04:51:01 字數 3064 閱讀 3302

重排序是指編譯器處理器為了優化程式效能而對指令序列進行重新排序的一種手段。

如果兩個操作訪問同乙個變數,且這兩個操作中有乙個為寫操作,此時這兩個操作之間就存在資料依賴性. 名稱

**說明

寫後讀a=1;b=a;

寫乙個變數後,再讀這個位置

寫後寫a=1;a=2

寫乙個變數後,再寫這個變數

讀後寫a=b;b=1;

讀乙個變數後,再寫這個變數

上面3種情況,只要重排序兩個操作的執行順序,程式的執行結果就會被改變。

前面提到過,編譯器和處理器可能會對操作做重排序。編譯器和處理器在重排序時,會遵守資料依賴性,編譯器和處理器不會改變存在資料依賴關係的兩個操作的執行順序。

這裡所說的資料依賴性僅針對單個處理器中執行的指令序列和單個執行緒中執行的操作,不同處理器之間和不同執行緒之間的資料依賴性不被編譯器和處理器考慮。

as-if-serial語義的意思是:不管怎麼重排序(編譯器和處理器為了提高並行度),(單執行緒)程式的執行結果不能被改變。

編譯器、runtime和處理器都必須遵守as-if-serial語義。

為了遵守as-if-serial語義,編譯器和處理器不會對存在資料依賴關係的操作做重排序,因為這種重排序會改變執行結果。

但是,如果操作之間不存在資料依賴關係,這些操作就可能被編譯器和處理器重排序。

舉個例子 : 計算圓面積

double pi =

3.14

;// a

double r =

1.0;

// b

double area = pi * r * r;

// c

上面3個操作的資料依賴關係如下所示

a和c之間存在資料依賴關係,同時b和c之間也存在資料依賴關係。因此最終執行的指令序列中,c不能被重排序到a和b的前面(c排到a和b的前面,程式的結果將會被改變)。

但a和b之間沒有資料依賴關係,編譯器和處理器可以重排序a和b之間的執行順序。

as-if-serial語義把單執行緒程式保護了起來,遵守as-if-serial語義的編譯器、runtime和處理器共同為編寫單執行緒程式的程式設計師建立了乙個幻覺:單執行緒程式是按程式的順序來執行的。as-if-serial語義使單執行緒程式設計師無需擔心重排序會干擾他們,也無需擔心記憶體可見性問題。

我們來看看,重排序是否會改變多執行緒程式的執行結果。 請看下面的示例**

public

class

asifserial

public

void

read()

}}

flag變數是個標記,用來標識變數a是否已被寫入。這裡假設有兩個執行緒a和b,a首先執行writer()方法,隨後b執行緒接著執行reader()方法。執行緒b在執行操作4時,能否看到執行緒a在操作1對共享變數a的寫入呢? ---------->不一定能看到.

由於操作1和操作2沒有資料依賴關係,編譯器和處理器可以對這兩個操作重排序;同樣操作3和操作4沒有資料依賴關係,編譯器和處理器也可以對這兩個操作重排序。

讓我們先來看看,當操作1和操作2重排序時,可能會產生什麼效果?

操作1和操作2做了重排序。程式執行時,執行緒a首先寫標記變數flag,隨後執行緒b讀這個變數。由於條件判斷為真,執行緒b將讀取變數a。此時,變數a還沒有被執行緒a寫入,在這裡多執行緒程式的語義被重排序破壞了! (虛箭線標識錯誤的讀操作)

再讓我們看看,當操作3和操作4重排序時會產生什麼效果(借助這個重排序,可以順便說明控制依賴性)。下面是操作3和操作4重排序後,程式執行的時序圖

在程式中,操作3和操作4存在控制依賴關係。當**中存在控制依賴性時,會影響指令序列執行的並行度。

為此,編譯器和處理器會採用猜測(speculation)執行來克服控制相關性對並行度的影響。以處理器的猜測執行為例,執行執行緒b的處理器可以提前讀取並計算a*a,然後把計算結果臨時儲存到乙個名為重排序緩衝(reorder buffer,rob)的硬體快取中。當操作3的條件判斷為真時,就把該計算結果寫入變數i中。

從上圖中我們可以看出,猜測執行實質上對操作3和4做了重排序。重排序在這裡破壞了多執行緒程式的語義!

在單執行緒程式中,對存在控制依賴的操作重排序,不會改變執行結果(這也是as-if-serial語義允許對存在控制依賴的操作做重排序的原因);但在多執行緒程式中,對存在控制依賴的操作重排序,可能會改變程式的執行結果。

咋改呢 ? 加 volatile

package com.artisan.test;

public

class

asifserial

public

void

read()

}public

static

void

main

(string[

] args)

throws interruptedexception

,"write").

start()

;// sleep一下 確保 write執行緒先啟動

thread.

sleep

(500);

newthread((

)->

,"read").

start();}}

Java 指令重排序與併發

一 指令重排序 編譯器或執行時環境 為了優化程式效能而採取的對指令進行重新排序執行的一種手段。指令重排序發生在不影響語義的情況下,也就是在單執行緒下,重排序不能導致執行結果發生變化。即遵循as if serial語義 進一步解釋就是在不影響執行結果的情況下,在jvm內的執行順序並不是嚴格按照書寫順序...

Java併發程式設計的藝術 二 重排序

當我們寫乙個單執行緒程式時,總以為計算機會一行行地執行 然而事實並非如此。重排序指的是編譯器 處理器在不改變程式執行結果的前提下,重新排列指令的執行順序,以達到最佳的執行效率。重排序分為 編譯器重排序 和 處理器重排序。編譯器和處理器並不會隨意的改變指令的執行順序,因為有些指令之間是有依賴關係的,若...

Java高併發程式設計筆記

jmm記憶體模型之 可見性 可見性是指當乙個執行緒修改了某乙個共享變數的值,其他執行緒是否能夠立即知道這個修改。jmm記憶體模型之 有序性 有序性問題的原因是因為程式在執行時,可能會出現指令重排,重排後的指令的順序未必一致。一條指令的執行可以分為很多步驟的 簡單來說就是以下幾步 1 取指if 2 解...