簡體   English   中英

C#易失性讀取行為

[英]C# Volatile read behavior

在C#.net ConcurrentDictionary( 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的字段?

易失性讀取具有獲取語義,這意味着它先於其他存儲器訪問。

如果它不是用於易失性讀取,那么下一次從我們剛剛得到的Node讀取的字段可以由JIT編譯器或體系結構推測地重新排序到讀取節點本身之前。

如果這沒有意義,想象一下JIT編譯器或體系結構讀取將分配給n任何值,並開始推測性地讀取 n.m_key ,這樣如果n != null ,則沒有錯誤預測的分支 ,沒有管道泡沫或更糟糕的是, 管道沖洗

當指令的結果可以用作下一條指令的操作數時,這仍然是可能 ,但是在流水線中。

對於易失性讀取或具有類似獲取語義的操作(例如,輸入鎖定),C#規范和CLI規范都說它必須在任何進一步的存儲器訪問之前發生,因此不可能獲得未初始化的n.m_key

也就是說,如果寫入也是易失性的,或者由具有類似釋放語義的操作(例如退出鎖定)保護。

如果沒有volatile語義,這種推測性讀取可能會返回n.m_key的未初始化值。

同樣重要的是comparer執行的內存訪問。 如果節點的對象是在沒有易失性版本的情況下初始化的,那么您可能正在閱讀陳舊的,可能是未初始化的數據。

這里需要Volatile.Read ,因為C#本身無法在數組元素上表達易失性讀取。 讀取m_next字段時不需要它,因為它聲明為volatile

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

簡短的回答:評論是錯誤的(或非常令人困惑)。
更長的答案: ConcurrentDictionary中至少有兩個錯誤

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM