Безопасность потоков с помощью словаряв .Net - PullRequest
9 голосов
/ 30 августа 2011

У меня есть эта функция:

static Dictionary<int, int> KeyValueDictionary = new Dictionary<int, int>();
static void IncreaseValue(int keyId, int adjustment)
{
    if (!KeyValueDictionary.ContainsKey(keyId))
    {
        KeyValueDictionary.Add(keyId, 0);
    }
    KeyValueDictionary[keyId] += adjustment;
}

То, что я бы подумал, не будет безопасным для потоков Однако до сих пор в тестировании я не видел никаких исключений при вызове его из нескольких потоков одновременно.

Мои вопросы: безопасна ли нить или мне просто повезло? Если это потокобезопасный, то почему?

Ответы [ 4 ]

21 голосов
/ 30 августа 2011

Однако до сих пор при тестировании я не видел никаких исключений при вызове его из нескольких потоков одновременно.

Это потокобезопасно или мне просто повезло? Если это потокобезопасный, то почему?

Тебе повезло. Эти типы ошибок с потоками так просто сделать, потому что тестирование может дать вам ложное чувство безопасности, что вы все сделали правильно.

Оказывается, Dictionary<TKey, TValue> не является потокобезопасным, когда у вас есть несколько писателей. В документации прямо говорится:

A Dictionary<TKey, TValue> может поддерживать несколько считывателей одновременно, если коллекция не изменена. Тем не менее, перечисление в коллекции по сути не является потокобезопасной процедурой. В редком случае, когда перечисление конкурирует с доступом для записи, коллекция должна быть заблокирована в течение всего перечисления. Чтобы разрешить доступ к коллекции из нескольких потоков для чтения и записи, необходимо реализовать собственную синхронизацию.

В качестве альтернативы используйте ConcurrentDictionary. Однако вы все равно должны написать правильный код (см. Примечание ниже).

В дополнение к отсутствию поточной безопасности с Dictionary<TKey, TValue>, которого вам посчастливилось избежать, ваш код опасно ошибочен. Вот как вы можете получить ошибку с вашим кодом:

static void IncreaseValue(int keyId, int adjustment) {
    if (!KeyValueDictionary.ContainsKey(keyId)) {
        // A
        KeyValueDictionary.Add(keyId, 0);
    }
    KeyValueDictionary[keyId] += adjustment;
}
  1. Словарь пуст.
  2. Поток 1 входит в метод с keyId = 17. Поскольку словарь пуст, условное выражение в if возвращает true, а поток 1 достигает строки кода, помеченной A.
  3. Поток 1 приостановлен, а поток 2 входит в метод с keyId = 17. Поскольку словарь пуст, условное выражение в if возвращает true, а поток 2 достигает строки кода, помеченной A.
  4. Тема 2 приостановлена, а тема 1 возобновлена. Теперь поток 1 добавляет (17, 0) в словарь.
  5. Тема 1 приостановлена, и теперь тема 2 возобновляется. Теперь поток 2 пытается добавить (17, 0) в словарь. Возникло исключение из-за нарушения ключа.

Существуют другие сценарии, в которых может возникнуть исключение. Например, поток 1 может быть приостановлен при загрузке значения KeyValueDictionary[keyId] (скажем, он загружает keyId = 17 и получает значение 42), поток 2 может войти и изменить значение (скажем, он загружает keyId = 17, добавляет корректировку 27), и теперь поток 1 возобновляет и добавляет свою корректировку к загруженному значению (в частности, он не видит изменения, внесенного потоком 2 в значение, связанное с keyId = 17!). *

Обратите внимание, что даже использование ConcurrentDictionary<TKey, TValue> может привести к вышеперечисленным ошибкам! Ваш код НЕ является безопасным по причинам, не связанным с безопасностью потоков или отсутствием таковых для Dictionary<TKey, TValue>.

Чтобы ваш код стал потокобезопасным с параллельным словарем, вам нужно будет сказать:

KeyValueDictionary.AddOrUpdate(keyId, adjustment, (key, value) => value + adjustment);

Здесь мы используем ConcurrentDictionary.AddOrUpdate.

2 голосов
/ 30 августа 2011

В библиотеке .NET есть многопоточный словарь, ConcurrentDictionary<TKey, TValue> http://msdn.microsoft.com/en-us/library/dd287191.aspx

Обновлено: я не совсем ответил на вопрос, поэтому здесь добавлено больше ответов на точный поставленный вопрос.Согласно MSDN: http://msdn.microsoft.com/en-us/library/xfhwa508.aspx

Словарь может поддерживать несколько читателей одновременно, если коллекция не изменена.Тем не менее, перечисление в коллекции по сути не является потокобезопасной процедурой.В редком случае, когда перечисление конкурирует с доступом для записи, коллекция должна быть заблокирована в течение всего перечисления.Чтобы разрешить доступ к коллекции из нескольких потоков для чтения и записи, необходимо реализовать собственную синхронизацию.

Для альтернативы, безопасной для потоков, см. ConcurrentDictionary.

Public static (Shared in VisualОсновные) члены этого типа являются потокобезопасными.

2 голосов
/ 30 августа 2011

Это не потокобезопасно, но не проверяет и поэтому, вероятно, не замечает молчаливого искажения.

Это будет казаться поточно-безопасным в течение длительного времени, потому что только тогда, когда ему требуется rehash (), это делаетесть даже шанс на исключение.В противном случае он просто повреждает данные.

1 голос
/ 30 августа 2011

Тебе только что повезло. Это не потокобезопасно.

Из документации Dictionary<K,V> ...

A Dictionary<TKey, TValue> может поддерживать несколько читателей одновременно, пока коллекция не изменена. Даже так, перечисление через коллекцию изначально не является потокобезопасным процедура. В редком случае, когда перечисление конкурирует с записью доступы, коллекция должна быть заблокирована в течение всего перечисления. Разрешить доступ к коллекции из нескольких потоков для чтения и написание, вы должны реализовать свою собственную синхронизацию.

...