多執行緒常見陷阱之重排序

2021-10-02 20:33:32 字數 1362 閱讀 3752

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

當一段**中的其中幾行資料不會出現資料依賴性的關係時,他就有可能被編譯器和處理器為了提高編譯器和處理器的並行度等原因而進行重排序。導致你原本可能想要的邏輯出現了本不能出現的bug。

先說說資料依賴性是什麼,然後我們再來看乙個出現重排序報錯的例項。

(一)資料依賴性

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

double a=10;  //操作a

double b=20; //操作b

double c=(a+b)*2; //操作c

此時,由於操作a和操作c之間存在了寫操作,且a是兩個操作共同訪問的變數,那麼此時操作a和c他們就存在資料依賴性。那麼操作a和c是不會出現重排序現象的。但是對於操作a和b,他們並沒有訪問同乙個變數,於是在編譯器和處理器進行優化時,他就有可能先進行操作b,再進行操作a,這樣的現象就是重排序。

(二)因重排序而導致的原本不可能出現的bug

在上訴的例子中,我們雖然產生了重排序,但是對於**的結果來說,並沒有任何問題,因為這是在單個執行緒中的例子。當牽扯到了多執行緒,那麼就可能會出現意想不到的問題。例如:

class example

public void reader()

} }

當有兩個執行緒x和y分別執行writer()和reader()方法時,由於操作a和操作b之間並沒有資料依賴性,因此他們可能會被重排序。這時候,如果出現執行緒x先執行了操作b,還未執行操作a時,執行緒y讀到了flag==true的結果,那麼操作c就會執行,最後i的值就會等於1。而我們想要的邏輯是希望當a=2時才進入操作c,i的預期結果應該是4。此時如果未考慮多執行緒就會出現了乙個無法理解的重排序的bug了。(有人會覺得操作c和操作d也可能會出現重排序問題。但是實際上並不會,因為當操作c和d出現重排序時,編譯器和處理器會進行猜測執行來對類似if之類的判斷進行出來,即使先執行了操作d,那也不會賦值給i,而是會先計算好a*a的結果,然後臨時儲存到乙個名為重排序緩衝的硬體快取中。只有操作c的條件滿足了,才會將快取的計算結果賦值給i)。

1.使用同步方法或者同步**塊synchronized執行多執行緒的**就能避免了。

2.當某個欄位是多個執行緒共同持有的共享變數,此時,我們可以使用volatile關鍵字來修飾他,例如上述**我們只要對a加上volatile關鍵字即可解決問題,即

class example

public void reader()

} }

因為volatile關鍵字修飾的變數會禁止他的方法體進行重排序(volatile的有序性)。

關於系統重排序對多執行緒的影響案例復現

最近一直在看 j a 併發程式設計的藝術 這本書,有一說一,書是真的不錯。但是在 p29 講到重排序對多執行緒的影響這個小結時,例項 邏輯上確實說的過去,但是我在 idea 一直無法復現,沒有重排序的現象出現。一切以事實說話,到底在多執行緒中有沒有重排序?或者直接說作業系統裡面有沒有對指令重排序。於...

多執行緒常見處理

多執行緒間 任務取消 多執行緒併發任務,某個失敗後,希望通知別的執行緒,都停下來,how?終止執行緒 向當前執行緒拋乙個異常然後終結任務 執行緒屬於os資源,可能不會立即停下來 task不能外部終止任務,只能自己終止自己 上帝才能打敗自己 cts有個bool屬性iscancellationreque...

C 常見陷阱之 語法

至少在2018的今天,c 的函式引數求值順序仍然是未定的 交給編譯器處理 所以函式引數求值的順序可能在某些情況下回導致一些問題。看下面這個例子 int test 5 printf d,d,d n test,test,test test 5 printf d,d,d n test test,test ...