2016-12-30 135 views
3

我正在看一個頁面上的例子。斯蒂芬·克利裏的書40,爲什麼這裏需要一個鎖?

// Note: this is not the most efficient implementation. 
// This is just an example of using a lock to protect shared state. 
static int ParallelSum(IEnumerable<int> values) 
{ 
    object mutex = new object(); 
    int result = 0; 
    Parallel.ForEach(source: values, 
     localInit:() => 0, 
     body: (item, state, localValue) => localValue + item, 
     localFinally: localValue => 
     { 
      lock (mutex) 
       result += localValue; 
     }); 
    return result; 
} 

和我有點困惑,爲什麼需要對lock。因爲如果我們所做的只是彙總了一組int s,比如說{1, 5, 6},那麼我們不需要關心以任何順序遞增的共享總和result

(1 + 5) + 6 = 1 + (5 + 6) = (1 + 6) + 5 = ... 

有人可以解釋我的思維在哪裏有缺陷嗎?

我猜我有點困惑該方法的身體不能簡單地

int result = 0; 
Parallel.ForReach(values, (val) => { result += val; }); 
return result; 

回答

2

聲明result += localValue;確實在說result = result + localValue;您正在閱讀和更新由不同線程共享的資源(變量)。這很容易導致競賽狀況。 lock確保在任何給定時刻這個語句被一個線程訪問。

10

運算,如加法不原子,因此沒有線程安全的。在示例代碼中,如果該鎖被省略,則完全有可能兩個附加操作幾乎同時執行,導致其中一個值被覆蓋或錯誤地添加。有一種方法線程安全地增加整數:Interlocked.Add(int, int)。但是,由於沒有被使用,所以在示例代碼中需要使用鎖來確保一次完成最多一次非原子加法操作(按順序而不是並行)。

+0

我認爲它的賦值操作符導致了這個問題。不是增加它自己。 –

+6

@ M.kazemAkhgary也不完全正確。賦值和加法都是獨立的原子。什麼*不*原子是增量,它由一個讀,一個添加,然後一個賦值組成。每三個操作都是100%原子,但所有三個操作都不是原子的。 – Servy

7

它不是在「結果」被更新的順序,其更新它,記住,運營商的race condition+=不是原子,所以兩個線程可能不會看到其他的線程的UDPATE他們觸摸它

+0

對我來說這似乎很奇怪,CPU甚至會讓這種碰撞發生。當我編寫C#時,我從不覺得必須考慮可能的硬件級錯誤。我想我應該是。 – user7127000

+0

請做!它與C或C++沒什麼區別 –

+2

@ user7127000它不是*錯誤*,它是一個基本特徵。如果CPU不允許重新排序操作,那麼你的計算機將比現在慢幾百倍。這些速度的許多數量級的代價是線程之間的共享狀態非常難以處理,所以你應該不惜一切代價避免這樣做,包括在你的例子中,因爲你的代碼會很多如果在單個線程上運行,而不是幾個,運行速度會更快。 – Servy