2016-05-17 52 views
4

在C#.NET ConcurrentDictionary(C# reference source)的參考源代碼,我不明白爲什麼在下面的代碼片段需要性讀:C#揮發性讀行爲

public bool TryGetValue(TKey key, out TValue value) 
{ 
    if (key == null) throw new ArgumentNullException("key"); 
     int bucketNo, lockNoUnused; 

    // We must capture the m_buckets field in a local variable. 
    It is set to a new table on each table resize. 
    Tables tables = m_tables; 
    IEqualityComparer<TKey> comparer = tables.m_comparer; 
    GetBucketAndLockNo(comparer.GetHashCode(key), 
         out bucketNo, 
         out lockNoUnused, 
         tables.m_buckets.Length, 
         tables.m_locks.Length); 

    // We can get away w/out a lock here. 
    // The Volatile.Read ensures that the load of the fields of 'n' 
    //doesn't move before the load from buckets[i]. 
    Node n = Volatile.Read<Node>(ref tables.m_buckets[bucketNo]); 

    while (n != null) 
    { 
     if (comparer.Equals(n.m_key, key)) 
     { 
      value = n.m_value; 
      return true; 
     } 
     n = n.m_next; 
    } 

    value = default(TValue); 
    return false; 
} 

註釋:

// We can get away w/out a lock here. 
// The Volatile.Read ensures that the load of the fields of 'n' 
//doesn't move before the load from buckets[i]. 
Node n = Volatile.Read<Node>(ref tables.m_buckets[bucketNo]); 

混淆了我一點點。

在從數組中讀取變量n本身之前,CPU如何讀取n的字段?

+1

我建議這是從[其他地方](http://referencesource.microsoft.com/#mscorlib/system/Collections/Concurrent/ConcurrentDictionary.cs,787)的複製和粘貼,因爲變量名不會在第二個例子中,當它們在這裏精確匹配時。儘管如此,我並沒有更接近辨別評論背後的意義。 –

+0

http://www.catb.org/jargon/html/Y/You-are-not-expected-to-understand-this.html您必須瞭解弱內存模型的處理器是如何工作的。他們在市場上表現不佳,只有ARM內核今天真的很重要。體面的博客文章[在這裏](http://preshing.com/20120930/weak-vs-strong-memory-models/)。 –

回答

2

易失性讀取已獲取語義,這意味着它先於其他內存訪問。

如果它不是用於易失性讀取,那麼從我們剛剛得到的Node開始,下一次讀取的字段可能會被JIT編譯器或體系結構推測性地重新排序,然後再讀取到節點本身。

如果這沒有意義,想象一個JIT編譯器或體系結構,讀取任何值將被分配到n,並開始speculatively readn.m_key,例如,如果n != null,沒有mispredicted branch,沒有pipeline bubble或者更糟,pipeline flushing

這是可能的when the result of an instruction can be used as an operand for the next instruction(s),但尚在籌備中。

對於volatile讀取或具有相似獲取語義的操作(例如,輸入鎖定),C#規範和CLI規範都聲明它必須在進一步訪問內存之前發生,因此無法獲得未初始化n.m_key。也就是說,如果寫操作也是易失性的或者被具有相似釋放語義的操作(例如退出鎖定)所保護。

沒有易失性語義,這樣的推測性讀取可能爲n.m_key返回未初始化的值。

同等重要的是由comparer執行的內存訪問。如果節點的對象在沒有volatile釋放的情況下被初始化,那麼您可能正在讀取陳舊的,可能未初始化的數據。

Volatile.Read在這裏是需要的,因爲C#本身沒有辦法在數組元素上表達一個volatile讀。閱讀m_next字段時不需要它,因爲它聲明爲volatile

0

非常感謝你提出了一個非常好的問題!

簡答題:評論是錯誤的(或非常混亂)。
稍微長一點的答案:至少有two bugs in ConcurrentDictionary

Volatile.Read<Node>(ref tables._buckets[bucketNo])Volatile.Read<Node>(ref buckets[i])是否有保護我們免於閱讀可能會被另一個線程更改的引用,因此我們有一個指向Node的具體實例的引用副本。
而且錯誤在於,即使對於這些Volatile.Read(因爲它不在評論中的原因),我們可以觀察到類型爲Node的部分構造對象,如果我們使用以下代碼分配參考:tables._buckets[bucketNo] = newNode