.NET4.0:更新字典及其值的线程安全方式

我有一个静态词典,我想安全更新。 最初,字典将为空,但在应用程序的生命周期内,它将添加新值。 此外,整数值将作为可以递增和递减的单个计数器。

private static Dictionary foo = new Dictionary(); public static void Add(string bar) { if (!foo.ContainsKey(bar)) foo.Add(bar, 0); foo[bar] = foo[bar] + 1; } public static void Remove(string bar) { if (foo.ContainsKey(bar)) { if (foo[bar] > 0) foo[bar] = foo[bar] - 1; } } 

我一直在阅读Interlocked类作为提供线程安全的方法,看起来这可能是我可以使用的东西:

 public static void Add(string bar) { if (!foo.ContainsKey(bar)) foo.Add(bar, 0); Interlocked.Increment(ref foo[bar]); } public static void Remove(string bar) { if (foo.ContainsKey(bar)) { if (foo[bar] > 0) Interlocked.Decrement(ref foo[bar]); } } 

然而,我的直觉是,当我实际向字典添加新项目时,这不会解决问题,因此立即想到锁定:

 private static Dictionary foo = new Dictionary(); private static object myLock = new object(); public static void Add(string bar) { lock(myLock) { if (!foo.ContainsKey(bar)) foo.Add(bar, 0); Interlocked.Increment(ref foo[bar]); } } public static void Remove(string bar) { lock(myLock) { if (foo.ContainsKey(bar)) { if (foo[bar] > 0) Interlocked.Decrement(ref foo[bar]); } } } 

这种方法是理想的,甚至是正确的吗? 可以改进吗? 我离开了吗?

lock是好的(并且是必要的;你怀疑Interlocked是不够的)但是一旦你这样做, Interlocked.IncrementInterlocked.Decrement是不必要的。 从多个线程访问Dictionary的问题是,一个线程可以触发内部哈希表的重建,然后该线程在中间重建时换出另一个线程,现在出现并添加到字典中造成破坏关于字典的内部结构。

此外,您的实现很好,因为您lock private对象而不是this 。 正如chibacity指出的那样,这个lock对象应该是readonly

小心你不要欺骗自己,以为你现在已经在multithreading场景中使你的字典防弹了。 例如,可能发生以下情况:

线程1查找字符串"Hello, World!" 在字典中,收到计数1

线程1被换出线程2。

线程2调用删除键"Hello, World!" 将计数设置为0

线程2被换出线程1。

线程1现在认为计数是1但实际上是0

最后,在.NET 4.0中,您应该考虑使用ConcurrentDictionary 。 请注意,这有效地消除了在字典上调用实例方法时lock的需要,但它并没有消除上述情况的发生。

如果您使用的是.Net 4.0,您可以考虑使用System.Collections.Concurrent命名空间中的集合,例如ConcurrentDictionary 可能适合您。

由于您已使用锁保护了添加和删除方法中对字典的multithreading访问,因此不需要互锁语句,因为此代码位于这些锁的范围内。 锁块中的所有代码现在都保证是单线程的,因此是安全的。

一个小点,但您应该将myLock对象标记为只读,因为目前可以通过重新分配来更改此对象。 因此,您可以更改对象,同时一个线程对其进行锁定,然后后续线程将看到不同的锁定对象,因此可以忽略先前的线程锁定。 将对象标记为只读使其不可变。

在您的情况下,字典上的add \ remove操作必须是线程安全的。 这是因为如果您在一个线程上枚举字典,则不希望任何其他线程对其进行修改。

要创建一个线程安全的字典,你应该在字典上创建一个包装器,它在私有IDictionary <>内部维护数据,并在Add,Remove,Clear等方法中使用lock。

请参阅此SOpost了解如何执行此操作 – 实现线程安全字典的最佳方法是什么?

或者,如果您使用.Net 4,那么您可以使用ConcurrentDictionary ,它提供开箱即用的线程安全性。

Monitor.Enter(),Monitor.Exit()(锁定(对象))可能是您最接近的最佳选择。 但是这个代码可以有所改进,尽管真正的好处可能是微不足道的。

  private static Dictionary foo = new Dictionary(); private static ReaderWriterLock rwLock= new ReaderWriterLock(); static int reads = 0; static int writes = 0; private static bool Contains(string bar) { try { rwLock.AquireReaderLock(TimeOut.Infinite); InterLocked.Increment(ref reads); return foo.ContainsKey(bar); } catch(Exception) { } finally { rwLock.ReleaseReaderLock(); } } public static void Add(string bar) { try { rwLock.AquireWriterLock(TimeOut.Infinite); if (!ContainsKey(bar)) { foo.Add(bar, 0); } foo[bar] = foo[bar] + 1; Interlocked.Increment(ref writes); } catch(Exception) {} finally { rwLock.ReleaseWriterLock(); } 

}

你会做同样的删除。