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就可以了。

 

posted @ 2026-02-17 19:58  没有星星的夏季  阅读(64)  评论(0)    收藏  举报