【问题标题】:Why using ReaderWriterLockSlim doesn't make my Dictionary thread safe?为什么使用 ReaderWriterLockSlim 不能使我的 Dictionary 线程安全?
【发布时间】:2011-06-16 14:23:03
【问题描述】:

我编写了一小段代码,可以从多个线程快速读取和写入字典。我使用 ReaderWriterLockSlim 来保护代码,但仍然收到了据称试图添加重复密钥的异常。

ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
Dictionary<int, int> _dict = new Dictionary<int, int>();

public SafeDictionaryTester()
{
    for (int i = 0; i < 7; i++)
    {
        _dict.Add(i, i);
    }
}

internal void Execute()
{
    for (int i = 7; i < 10000; i++)
    {
        if (i % 6 == 0)
            new Thread(new ThreadStart(delegate { Print(6); })).Start();
        else if (i % 5 == 0)
            new Thread(new ThreadStart(delegate { Print(5); })).Start();
        else if (i % 4 == 0)
            new Thread(new ThreadStart(delegate { Print(4); })).Start();
        else if (i % 3 == 0)
            new Thread(new ThreadStart(delegate { Print(3); })).Start();
        else if (i % 2 == 0)
            new Thread(new ThreadStart(delegate { Print(2); })).Start();
        else if (i % 1 == 0)
            new Thread(new ThreadStart(delegate { Print(1); })).Start();

        new Thread(new ThreadStart(delegate
        {
            _lock.EnterWriteLock();
            try
            {
                _dict.Add(i, i); // Exception after random number of loops
                Console.WriteLine(i.ToString() + " added");
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        })).Start();
    }
}

private void Print(int i)
{
    _lock.EnterReadLock();
    try
    {
        int obj;
        if (_dict.TryGetValue(i, out obj))
        {
            Console.WriteLine(obj);
        }
        else
        {
            throw new Exception();
        }
    }
    finally
    {
        _lock.ExitReadLock();
    }
}

请注意,没有线程的确切代码可以完美执行。

【问题讨论】:

    标签: c# .net multithreading dictionary readerwriterlockslim


    【解决方案1】:

    问题是您的匿名作家委托在i 之上创建了一个closure

    也就是说,当您的编写器线程执行时,它们将使用i当前值,而不是线程启动时的值 (7、8、9 ...等)

    要修复它,您需要在 for 循环中复制变量并在编写器委托中使用它:

    internal void Execute()
    {
        for (int i = 7; i < 10000; i++)
        {
            // trimmed for brevity: create a copy of i
            int copy = i;
    
            new Thread(new ThreadStart(delegate
            {
                _lock.EnterWriteLock();
                try
                {
                    _dict.Add(copy, copy); // Exception after random number of loops
                    Console.WriteLine(copy.ToString() + " added");
                }
                finally
                {
                    _lock.ExitWriteLock();
                }
            })).Start();
        }
    

    【讨论】:

      【解决方案2】:

      正如 Ani 所说,这与字典无关。你真的(可能)试图添加相同的键两次,因为你正在捕获循环变量。简单的解决方法是将循环变量复制到循环中的一个新变量,这样每个额外的线程将只能“看到”它自己的值。

      for (int i = 7; i < 10000; i++)
      {
          // Other stuff...
          copyOfI = i;
      
          new Thread(new ThreadStart(delegate
          {
              _lock.EnterWriteLock();
              try
              {
                  _dict.Add(copyOfI, copyOfI);
                  Console.WriteLine(copyOfI.ToString() + " added");
              }
              finally
              {
                  _lock.ExitWriteLock();
              }
          })).Start();
      }
      

      有关更多信息,请参阅 Eric Lippert 的博客文章:part 1part 2.

      【讨论】:

      • 你的链接更合适,但我发现我的链接读起来更好。 ;)
      • @Jeff:在某些方面,我赢了;)
      • 谢谢乔恩。我又被捕获的变量愚弄了。请注意,Print(1) 不是问题(请参阅 ctor 类)。 真正的问题是:我需要使用锁定吗?运行此测试(使用不同的变体)不会产生任何异常。
      • @HuBeZa:啊,我没发现。是的,您需要使用锁定 - 即使您没有遇到异常,也不能保证您不会搞砸内部状态。 Dictionary 对于多线程使用根本不安全,除非它纯粹用于读取。请注意,如果您使用的是 .NET 4,ConcurrentDictionary 可能是更好的选择。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-03
      • 2012-11-20
      • 2013-10-11
      • 2020-10-10
      • 1970-01-01
      相关资源
      最近更新 更多