为什么使用ReaderWriterLockSlim不会使我的Dictionary线程安全?

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

标签: c# .net multithreading dictionary readerwriterlockslim

我写了一小段代码,可以快速读取和写入多个线程的字典。我使用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();
    }
}

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

2 个答案:

答案 0 :(得分:5)

问题是您的匿名作家代表会在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();
    }

答案 1 :(得分:4)

正如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 1; part 2