澄清C#字典的读写

时间:2011-01-14 20:32:27

标签: c# dictionary thread-safety

在本声明的背景下,

  

字典可以支持   同时多个读者,同样长   因为集合没有被修改。   即便如此,通过一个列举   集合本质上不是一个   线程安全的程序。在罕见的   枚举争辩的情况   与写访问,集合   必须在整个过程中锁定   列举。允许收集   由多个线程访问   读书和写作,你必须   实现自己的同步。

读写意味着什么?我的理解是,读操作是查找键并提供对其值的引用的操作,而写操作是从字典中添加或删除键值对的操作。但是,关于这一点,我找不到任何结论。

所以最大的问题是,在实现线程安全字典时,更新字典中现有键值的操作是否会被视为读者或作者?我计划让多个线程访问字典中的唯一键并修改它们的值,但线程不会添加/删除新键。

明显的暗示,假设修改现有值不是字典上的写操作,我的线程安全字典的实现可以更高效,因为我不需要每次都获得独占锁尝试将值更新为现有密钥。

不能使用.Net 4.0中的ConcurrentDictionary。

6 个答案:

答案 0 :(得分:3)

尚未提及的一个要点是,如果TValue是类类型,则Dictionary<TKey,TValue>所持有的内容将是TValue个对象的标识 。如果从字典中接收到引用,则字典既不会知道也不会关心可能对所引用的对象做什么。

一个有用的小实用程序类,在与需要使用它的代码之前知道与字典关联的所有键的情况下:

class MutableValueHolder<T>
{
   public T Value;
}

如果想要多线程代码计算各种字符串出现在一堆文件中的次数,并且事先知道所有感兴趣的字符串,则可以使用类似Dictionary<string, MutableValueHolder<int>>之类的字符串。目的。一旦字典加载了所有正确的字符串并为每个字符加载MutableValueHolder<int>实例,那么任意数量的线程都可以检索对MutableValueHolder<int>个对象的引用,并使用Threading.Interlocked.Increment或其他此类方法来修改与每个Value相关联的{{1}},而不必写入字典。

答案 1 :(得分:2)

覆盖现有值应视为写入操作

答案 2 :(得分:0)

任何可能影响另一次读取结果的内容都应被视为写入。

更改密钥最明确的是写入,因为它会导致项目在内部哈希或索引中移动,或者字典会执行其O(log(n))内容...

您可能想要做的是查看ReaderWriterLock

http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlock.aspx

答案 3 :(得分:0)

更新值在概念上是写操作。在更新具有并发访问权限的值时,如果在写入完成之前执行读取,则会读出旧值。当两个写入冲突时,可能存储错误的值。

添加新值可能会触发底层存储的增长。在这种情况下,分配新内存,将所有元素复制到新内存中,添加新元素,更新字典对象以引用新内存位置以进行存储,并释放旧内存并可用于垃圾回收。在此期间,更多写入可能会导致严重问题。同时两次写入可能会触发此内存复制的两个实例。如果你遵循逻辑,你会看到一个元素会丢失,因为只有最后一个更新引用的线程才会知道现有的项目而不是其他试图添加的项目。

ICollection provides a member to synchronize access和引用在增长/缩减操作中保持有效。

答案 4 :(得分:0)

读取操作是从Dictionary获取键或值的任何操作,写入操作是更新或添加键或值的任何操作。因此,更新密钥的过程将被视为作者。

创建线程安全字典的一种简单方法是创建自己的IDictionary实现,只需锁定互斥锁,然后将调用转发给实现:

public class MyThreadSafeDictionary<T, J> : IDictionary<T, J>
{
      private object mutex = new object();
      private IDictionary<T, J> impl;

      public MyThreadSafeDictionary(IDictionary<T, J> impl)
      {
          this.impl = impl;
      }

      public void Add(T key, J value) 
      {
         lock(mutex) {
             impl.Add(key, value);
         }
      }

      // implement the other methods as for Add
}

如果您的某些线程只读取字典,则可以使用读写器锁替换互斥锁。

另请注意Dictionary个对象不支持更改密钥;实现您想要的唯一安全方法是删除现有的键/值对,并添加一个带有更新键的新键。

答案 5 :(得分:-1)

修改值是写入并引入竞争条件。

让我们说mydict [5]的原始值= 42。 一个线程将mydict [5]更新为112。 另一个线程将mydict [5]更新为837。

mydict [5]的价值到底应该是什么?在这种情况下,线程的顺序很重要,这意味着您需要确保顺序是明确的,或者它们不会写入。