Почему использование 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 ответа
Как говорит Ани, это не имеет ничего общего со словарем. Вы действительно (вероятно) пытаетесь добавить один и тот же ключ дважды, потому что вы захватываете переменную цикла. Простое решение - скопировать переменную цикла в новую переменную внутри цикла, чтобы каждый дополнительный поток только "видел" свое собственное значение.
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();
}
См. Сообщения Эрика Липперта в блоге для получения дополнительной информации: часть 1; часть 2
Проблема в том, что ваш анонимный автор-писатель создает закрытие над 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();
}