简体   繁体   中英

Is it safe to access a reference object that will be updated in another thread in C#?

There were some similar questions on stackoverflow about that, but I did not quite get the answer to the case that I have at the moment:

  • I have some kind of a cache in a dictionary that is accessed in a multiple threads
  • It does not matter if aa thread access the "old" version if an update is in process

    This is how I access the dictionary:

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

    This is how I update the reference when some element is not in the cache eg (I can have multiple updates):

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

So the question is: will I run into some issues with this, namely reading some partially updated reference? Since it's not 32 bit value (it can run on 64 bit architecture) it seems to me that it's not safe as atomicity is not guaranteed. Will it be enough then to change all the read access from variable to this method:

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

This part is unsafe:

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]);
}

for that reason you would have to surround it with a lock (m_Lock){} too.

But your requirements should be met with:

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

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

Since you're updating the entire dictionary in one go, the actual access of the reference is safe.

Since it's not 32 bit value (it can run on 64 bit architecture) it seems to me that it's not safe as atomicity is not guaranteed.

On a 64-bit architecture, 64-bit-aligned 64-bit values have atomic access. Reference accesses are always atomic.

However, there are two things that can still go wrong:

One of these is updates from one core not being seen with another. You don't have this issue since you write in a lock and .NET locks have an implicit memory barrier. (Indeed, you don't need a full lock, a volatile write will suffice).

The other is races between reads. the m_dictionary of one read may not be the m_dictionary of the next. Read it into a local variable, and then act upon that local.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM