C#中ConcurrentDictionary的AddOrUpdate/GetOrAdd的并发问题
这个是偶然发现的,我们看下面的代码:
public static void Main()
{
int addCount = 0;
int updateCount = 0;
var dict = new ConcurrentDictionary<int, int>();
for (var i = 0; i < 10; i++)
{
new Thread(() =>
{
dict.AddOrUpdate(1, _ => Interlocked.Increment(ref addCount), (_, _) => Interlocked.Increment(ref updateCount));
}).Start();
}
Console.WriteLine($"addCount={addCount}");
Console.WriteLine($"updateCount={updateCount}");
}
你是否会觉得输出addCount=1,updateCount=9?就是addValueFactory只会被调用一次?调用了addValueFactory就不会调用updateValueFactory?
再看下面的代码:
public static void Main()
{
int addCount = 0;
var dict = new ConcurrentDictionary<int, int>();
for (var i = 0; i < 10; i++)
{
new Thread(() =>
{
dict.GetOrAdd(1, _ => Interlocked.Increment(ref addCount));
}).Start();
}
Console.WriteLine($"addCount={addCount}");
}
你是否会觉得输出addCount=1?就是valueFactory只会被调用一次?
如果你这么觉得,那你就错了,同样的,我也被骗了这么多年!!!结果是不确定的!!
微软只说ConcurrentDictionary是线程安全的,却没说它体现在哪里,我们看看AddOrUpdate和GetOrAdd的源码就知道了
AddOrUpdate
public TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
{
if (key is null)
{
ThrowHelper.ThrowKeyNullException();
}
if (addValueFactory is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(addValueFactory));
}
if (updateValueFactory is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(updateValueFactory));
}
IEqualityComparer<TKey>? comparer = _comparer;
int hashcode = comparer is null ? key.GetHashCode() : comparer.GetHashCode(key);
while (true)
{
if (TryGetValueInternal(key, hashcode, out TValue? oldValue))
{
// key exists, try to update
TValue newValue = updateValueFactory(key, oldValue);
if (TryUpdateInternal(key, hashcode, newValue, oldValue))
{
return newValue;
}
}
else
{
// key doesn't exist, try to add
if (TryAddInternal(key, hashcode, addValueFactory(key), updateIfExists: false, acquireLock: true, out TValue resultingValue))
{
return resultingValue;
}
}
}
}
GetOrAdd
public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
{
if (key is null)
{
ThrowHelper.ThrowKeyNullException();
}
if (valueFactory is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(valueFactory));
}
IEqualityComparer<TKey>? comparer = _comparer;
int hashcode = comparer is null ? key.GetHashCode() : comparer.GetHashCode(key);
if (!TryGetValueInternal(key, hashcode, out TValue? resultingValue))
{
TryAddInternal(key, hashcode, valueFactory(key), updateIfExists: false, acquireLock: true, out resultingValue);
}
return resultingValue;
}
它们内部都没有上锁,AddOrUpdate就是在一个while(true)循环内,不停的尝试修改和添加,每次循环或者调用addValueFactory或者updateValueFactory,GetOrAdd获取不到值就会去调用valueFactory,所以,如果你在这些factory里面有并发操作的话,一定要注意!!
那怎么解决这个问题呢,你说在每个地方上锁吧,好像很繁琐,我的解决办法是使用拓展方法来解决:
DictionaryExtensions
public static class DictionaryExtensions
{
public static TValue AddOrUpdateValue<TKey, TValue, TArg>(this ConcurrentDictionary<TKey, TValue> dictionary,
TKey key, Func<TKey, TArg, TValue> addValueFactory, Func<TKey, TValue, TArg, TValue> updateValueFactory, TArg factoryArgument)
{
bool hasAddValue = false;
var addValue = default(TValue);
bool hasUpdateValue = false;
var updateValue = default(TValue);
Func<TKey, TArg, TValue> newAddValueFuctory = (key, arg) =>
{
if (!hasAddValue)
{
addValue = hasUpdateValue ? updateValue : addValueFactory(key, arg);
hasAddValue = true;
}
return addValue;
};
Func<TKey, TValue, TArg, TValue> newUpdateValueFactory = (key, value, arg) =>
{
if (!hasUpdateValue)
{
addValue = hasAddValue ? addValue : updateValueFactory(key, value, arg);
hasAddValue = true;
}
return updateValue;
};
lock (dictionary)
{
return dictionary.AddOrUpdate(key, newAddValueFuctory, newUpdateValueFactory, factoryArgument);
}
}
public static TValue AddOrUpdateValue<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dictionary, TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
{
bool hasAddValue = false;
var addValue = default(TValue);
bool hasUpdateValue = false;
var updateValue = default(TValue);
Func<TKey, TValue> newAddValueFuctory = key =>
{
if (!hasAddValue)
{
addValue = hasUpdateValue ? updateValue : addValueFactory(key);
hasAddValue = true;
}
return addValue;
};
Func<TKey, TValue, TValue> newUpdateValueFactory = (key, value) =>
{
if (!hasUpdateValue)
{
addValue = hasAddValue ? addValue : updateValueFactory(key, value);
hasUpdateValue = true;
}
return updateValue;
};
lock (dictionary)
{
return dictionary.AddOrUpdate(key, newAddValueFuctory, newUpdateValueFactory);
}
}
public static TValue AddOrUpdateValue<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dictionary, TKey key, TValue addValue, Func<TKey, TValue, TValue> updateValueFactory)
{
bool hasUpdateValue = false;
var updateValue = default(TValue);
Func<TKey, TValue, TValue> newUpdateValueFactory = (key, value) =>
{
if (!hasUpdateValue)
{
addValue = updateValueFactory(key, value);
hasUpdateValue = true;
}
return updateValue;
};
lock (dictionary)
{
return dictionary.AddOrUpdate(key, addValue, newUpdateValueFactory);
}
}
public static TValue GetOrAddValue<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> dictionary, TKey key, Func<TKey, TValue> valueFactory)
{
bool hasAddValue = false;
var addValue = default(TValue);
Func<TKey, TValue> newAddValueFuctory = key =>
{
if (!hasAddValue)
{
addValue = valueFactory(key);
hasAddValue = true;
}
return addValue;
};
lock (dictionary)
{
return dictionary.GetOrAdd(key, newAddValueFuctory);
}
}
public static TValue GetOrAddValue<TKey, TValue, TArg>(this ConcurrentDictionary<TKey, TValue> dictionary, TKey key, Func<TKey, TArg, TValue> valueFactory, TArg factoryArgument)
{
bool hasAddValue = false;
var addValue = default(TValue);
Func<TKey, TArg, TValue> newAddValueFuctory = (key, arg) =>
{
if (!hasAddValue)
{
addValue = valueFactory(key, arg);
hasAddValue = true;
}
return addValue;
};
lock (dictionary)
{
return dictionary.GetOrAdd(key, newAddValueFuctory, factoryArgument);
}
}
}
这个是简单的封装,满足我们之前一如既往的认识,向上面的例子,我们把AddOrUpdate和GetOrAdd分表改成AddOrUpdateValue和GetOrAddValue就可以了。
一个专注于.NetCore的技术小白

浙公网安备 33010602011771号