標籤:使用 資料 問題 cti io 時間
目錄
Singleton樣本
volatile解決問題1:CPU緩衝
volatile解決問題2:編譯器最佳化(指令亂序)
一. 標準的單例模式樣本```csharppublic sealed class Singleton{ // 靜態執行個體 private static volatile Singleton instance = null; // Lock對象,安全執行緒所用 private static object syncRoot = new Object();
private Singleton() { }public static Singleton Instance{ get { if (instance == null) //一次比較 { lock (syncRoot) { if (instance == null) //二次比較 instance = new Singleton(); } } return instance; }}
}
```
注意靜態樣本自動前的修飾符: volatile,為什麼必須指定volatile, 如果不使用該關鍵字,會有什麼後果呢。
volatile【易變的】,查msdn: volatile 關鍵字指示一個欄位可以由多同時執行的線程修改。 聲明為 volatile 的欄位不受編譯器最佳化(假定由單個線程訪問)的限制。 這樣可以確保該欄位在任何時間呈現的都是最新的值。
那在底層到底發生了什麼,難道不使用volatile,指令執行時,欄位的值還不是新的嗎,這得從電腦架構及編譯器最佳化兩個方面說起。
二. CPU緩衝問題
採用volatile關鍵字,每次讀欄位值時,都必須從記憶體中擷取,也就是說,對該欄位禁用CPU緩衝。
由於現代的CPU都存在多個核心,每個核心有獨立的內部緩衝,而對象欄位最初是儲存在記憶體中的,執行指令前,會首先檢查緩衝中是否存在該欄位,如果沒有,從記憶體中讀取資料到CPU緩衝,進行運算, 如果緩衝存在,則無需訪問記憶體,直接使用緩衝的資料。
針對單例模式樣本,兩個線程分別在兩個CPU核心運行,兩個核心同時運行到上面語句“第一次比較”,instance欄位都儲存在各個CPU的內部緩衝中,通過lock關鍵字,兩個線程會串列執行lock語句塊。
比如核心A已經運行完成lock語句塊,記憶體中的instance已經更新,此時核心B繼續運行,由於核心B已經緩衝了instance樣本,在二次比較時,還是認為instance欄位為空白,這樣就導致 new Singleton()執行了兩次。
三. 編譯器最佳化問題
採用volatile關鍵字, 可以避免指令重新排序(instruction reordering), 例如,考慮如下一個迴圈:```csharpwhile(true){ if(myField) { //do something }}
```
如果myField欄位沒有指定volatile, 在JIT編譯時間,出於效能最佳化考慮,編譯器會以如下方式重新排序指令:```csharp
if(myField){ while(true) { //do something }}
```
這種情況下,如果你在另一個線程中修改欄位myFiled, 運行結果將完成不同。通常情況下,推薦使用lock語句(Monitor.Enter /Monitor.Exit),但如果你在這個語句塊內僅僅修改了一個欄位,volatile將有更好的效能表現。