Почему использование ReaderWriterLockSlim не делает мою цепочку словаря безопасной? - PullRequest
2 голосов
/ 27 января 2011

Я написал небольшой фрагмент кода, который быстро читает и записывает в словарь из нескольких потоков.Я использовал 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 ]

5 голосов
/ 27 января 2011

Проблема в том, что ваш анонимный делегат-писатель создает замыкание через i.

То есть, когда ваши потоки записи выполняются, они будут использовать текущее значение из 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();
    }
4 голосов
/ 27 января 2011

Как говорит Ани, это не имеет ничего общего со словарем. Вы действительно (вероятно) пытаетесь добавить один и тот же ключ дважды, потому что вы захватываете переменную цикла. Простое решение - скопировать переменную цикла в новую переменную внутри цикла , чтобы каждый дополнительный поток только "видел" свое собственное значение.

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();
}

См. Сообщения Эрика Липперта в блоге для получения дополнительной информации: part 1 ; часть 2 .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...