ConcurrentDictionary Pitfall - Являются ли делегированные заводы из GetOrAdd и AddOrUpdate синхронизированными?
Документация ConcurrentDictionary
не содержит явного состояния, поэтому я думаю, мы не можем ожидать, что делегаты valueFactory
и updateValueFactory
синхронизируются с их выполнением (из операций GetOrAdd() и AddOrUpdate() соответственно).
Итак, я думаю, что мы не можем реализовать использование ресурсов внутри них, которые нуждаются в параллельном управлении без ручного внедрения нашего собственного параллельного элемента управления, возможно, просто используя [MethodImpl(MethodImplOptions.Synchronized)]
над делегатами.
Я прав? Или тот факт, что ConcurrentDictionary
является потокобезопасным, мы можем ожидать, что вызовы этих делегатов будут автоматически синхронизированы (поточно-безопасные тоже)?
Ответы
Ответ 1
Да, вы правы, делегаты пользователей не синхронизируются с помощью ConcurrentDictionary
. Если вам нужны синхронизированные, это ваша ответственность.
Сам MSDN говорит:
Кроме того, хотя все методы ConcurrentDictionary являются поточно-безопасный, не все методы являются атомарными, в частности GetOrAdd и AddOrUpdate. Делегат пользователя, который передается этим методам, вызывается вне внутренней блокировки словаря. (Это делается для запретить неизвестный код блокировать все потоки.)
См." Как добавить и удалить элементы из ConcurrentDictionary
Это связано с тем, что ConcurrentDictionary
не имеет представления о том, что сделает предоставленный вами делегат, или о его производительности, поэтому, если он попытается заблокировать их, это может негативно повлиять на производительность и разрушить значение ConcurrentDictionary.
Таким образом, ответственность за синхронизацию их делегата несет пользователь, если это необходимо. Ссылка MSDN выше на самом деле имеет хороший пример гарантий, которые она делает и не делает.
Ответ 2
Эти делегаты не только не синхронизированы, но даже не гарантируются, что они будут выполняться только один раз. Фактически они могут выполняться несколько раз за вызов до AddOrUpdate
.
Например, алгоритм для AddOrUpdate
выглядит примерно так.
TValue value;
do
{
if (!TryGetValue(...))
{
value = addValueFactory(key);
if (!TryAddInternal(...))
{
continue;
}
return value;
}
value = updateValueFactory(key);
}
while (!TryUpdate(...))
return value;
Обратите внимание на две вещи.
- Нет необходимости синхронизировать выполнение делегатов.
- Делегаты могут выполняться несколько раз, так как они вызываются внутри цикла.
Итак, вам нужно убедиться, что вы делаете две вещи.
- Предоставьте свою собственную синхронизацию для делегатов.
- Убедитесь, что ваши делегаты не имеют никаких побочных эффектов, которые зависят от количества выполняемых им времени.