访问将在C#中的另一个线程中更新的引用对象是否安全?

时间:2015-10-29 12:50:42

标签: c# .net multithreading thread-safety

关于stackoverflow有一些类似的问题,但我现在还没有完全得到答案:

  • 我在字典中有某种缓存,可以通过多线程访问
  • 如果正在进行更新,一个线程是否访问“旧”版本无关紧要

    这是我访问字典的方式:

    if (m_Dictionary != null && m_Dictionary.ContainsKey(key))
    {
        return await Task.FromResult(m_Dictionary[key]);
    }
    

    这是当一些元素不在缓存中时更新引用的方式,例如(我可以有多个更新):

    var newDictionary = Load();
    lock (m_Lock)
    {
        m_Dictionary = newDictionary;
    }
    

所以问题是:我会遇到一些问题,即阅读一些部分更新的参考?由于它不是32位值(它可以在64位架构上运行),在我看来它不安全,因为原子性不能得到保证。那么将所有读取访问权限从变量更改为此方法就足够了:

private Dictionary<string, int> GetDictionary()
{
    lock (m_Lock)
    {
       return m_Dictionary;
    }
}

2 个答案:

答案 0 :(得分:2)

这部分不安全:

if (m_Dictionary != null && m_Dictionary.ContainsKey(key))
{
    // this could still throw KeyNotFound because 
    // it can be a different Dictionary than in the previous line
    return await Task.FromResult(m_Counterparts[key]);
}

因此你必须用lock (m_Lock){}包围它。

但是你的要求应该得到满足:

var localDictionary = GetDictionary();  // a local copy is thread-safe

if (localDictionary.ContainsKey(key))   // or TryGetvalue
{
    return await Task.FromResult(localDictionary[key]);
}
else ...

答案 1 :(得分:2)

由于您一次性更新整个字典,因此实际访问该引用是安全的。

  

由于它不是32位值(它可以在64位架构上运行),在我看来它不安全,因为原子性不能得到保证。

在64位体系结构中,64位对齐的64位值具有原子访问权限。参考访问总是原子的。

然而,有两件事仍然可能出错:

其中一个是来自一个核心的更新,而不是另一个核心。由于您在锁中写入并且.NET锁具有隐式内存屏障,因此您不会遇到此问题。 (实际上,你不需要一个完整的锁定,一个易变的写入就足够了。)

另一个是阅读之间的比赛。一次阅读的m_dictionary可能不是下一次的m_dictionary。将其读入本地变量,然后对该本地变量进行操作。