字典不安全的线程 - 让我们打破一些东西

时间:2011-12-23 18:21:59

标签: c# .net multithreading dictionary

所以在http://msdn.microsoft.com/en-us/library/xfhwa508.aspx的底部我们看到了

  

字典(Of TKey,TValue)可以同时支持多个阅读器,只要不修改集合即可。即便如此,通过集合枚举本质上不是一个线程安全的过程。在枚举与写访问争用的极少数情况下,必须在整个枚举期间锁定该集合。要允许多个线程访问集合以进行读写,您必须实现自己的同步。

在我的asp.net应用程序中,我偶尔会看到从System.Collections.Generic.Dictionary`2.Insert内部生成的NullReferenceExceptions。

如何在沙箱中重新创建?

以下代码不会导致异常,尽管本地字典由不同的线程访问。

class Program {
    static void Main(string[] args) {
        int maxThreads = 20;
        Dictionary<Guid, Guid> dict = new Dictionary<Guid, Guid>();
        var tws = new threadWithState();
        while (true) {
            for (int i = 0; i < maxThreads * 2; i++) {
                var thread = new Thread(tws.Work);
                thread.IsBackground = true;
                thread.Start(dict);
            }
            dict[Guid.NewGuid()] = Guid.NewGuid();
        }
    }
}

class threadWithState {
    public void Work(object dict) {
        Console.WriteLine((dict as Dictionary<Guid, Guid>).Count);
        for (int i = 0; i < 1000; i++) {
            (dict as Dictionary<Guid, Guid>)[Guid.NewGuid()] = Guid.NewGuid();
        }
    }
}

2 个答案:

答案 0 :(得分:8)

这是线程问题的一部分 - 错误不一定可靠地重现。问题是插入必须达到特定的竞争条件,这显然不会在您的测试中发生。当然,足够的运行可能会导致触发,但这很难可靠地进行测试。

话虽如此,这显然是一个问题。你应该考虑如何纠正它,而不是专注于复制它。当然,这里显而易见的答案是切换到另一种技术,例如使用ConcurrentDictionary<T,U>代替,这是线程安全的。

答案 1 :(得分:3)

由于垃圾收集堆锁,此代码遭受意外同步。通过注入随机延迟,您可以更快地崩溃,就像在实际服务器上发生的那样。这段代码在我的机器上只有一秒钟就崩溃了,ymmv:

        public void Work(object dict) {
            var rnd = new Random();
            Console.WriteLine((dict as Dictionary<Guid, Guid>).Count);
            for (int i = 0; i < 1000000; i++) {
                (dict as Dictionary<Guid, Guid>)[Guid.NewGuid()] = Guid.NewGuid();
                for (int ix = 0; ix < rnd.Next(1000); ++ix) ;   // NOTE: random delay
            }
        }