Safely removing list mapping from ConcurrentDictionary

☆樱花仙子☆ 提交于 2020-03-19 06:06:39

问题


I have a ConcurrentDictionary which maps a simple type to a list:

var dict = new ConcurrentDictionary<string, List<string>>();

I can use AddOrUpdate() to cater for both initialization of the list when the first value is added, and addition of subsequent values to the list.

However, the same isn't true for removal. If I do something like:

public void Remove(string key, string value)
{
    List<string> list;
    var found = dict.TryGetValue(key, out list);

    if (found)
    {
        list.Remove(value);
        if (list.Count == 0)
        {
            // warning: possible race condition here
            dict.TryRemove(key, out list);
        }
    }
}

...where my intention is to remove the key completely if the corresponding list no longer has any values (similar to reference counting, in concept), then I'm risking a race condition because someone might have added something to the list right after I checked whether it's empty.

Although I am using a list in this simple example, I usually have a ConcurrentBag or ConcurrentDictionary in such scenarios, and the risk is quite similar.

Is there any way of safely removing a key when the corresponding collection is empty, short of resorting to locks?


回答1:


Your ConcurrentDictionary is protected but your list is not. If your list can be accessed from multiple threads (I assume this is the case) you need to use locking around all accesses to the list or you need to use a different construct.

After you call TryGetValue in your Remove function, you access the list multiple times - since List<T> is not safe for multithreading, you run the risk of various threading issues.

If you use nested ConcurrentDictionaries in dict, you only run the problem of removing something that's not empty - as you wrote, it's possible that an item is added to the nested ConcurrentDictionary after you check its size. Removing the nested list / dictionary itself is thread-safe: the containing dict is a ConcurrentDictionary and it'll handle removing items safely. However, if you want to guarantee that a list/dictionary is only removed if it's empty, you have to use a lock around the whole operation.

This is because the container dict and the nested list/dictionary are two different constructs and touching one has no effect on the other - if you need the entire multi-step operation to be atomic, you'll have to make sure only one thread can attempt to do it at a time.

Your code would be something like this:

if (found)
{
    lock ( _listLock )
    {
        list.Remove(value);

        if (list.Count == 0)
        {
            // warning: possible race condition here
            dict.TryRemove(key, out list);
        }
    }
}

Again, if you're using an unprotected construct (like a List<T> then you have to use locking around every access to that list.



来源:https://stackoverflow.com/questions/27297326/safely-removing-list-mapping-from-concurrentdictionary

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!