两个线程/任务同时命中相同功能的机会

时间:2017-04-01 10:14:49

标签: c# multithreading locking task

假设以下情况:

public HashTable map = new HashTable();
public void Cache(String fileName) {
        if (!map.ContainsKey(fileName))
        {
            map.Add(fileName, new Object());
            _Cache(fileName);
        }
    }
}
private void _Cache(String fileName) {
        lock (map[fileName])
        {
            if (File Already Cached)
                return;
            else {
                cache file
            } 
        }
    } 

拥有以下消费者时:

Task.Run(()=> {
    Cache("A");
});
Task.Run(()=> {
    Cache("A");
});

是否有可能Cache方法会抛出Duplicate key异常,这意味着两个任务都会遇到map.add方法并尝试添加相同的密钥?< / p>

编辑:

使用以下数据结构会解决这个并发问题吗?

public class HashMap<Key, Value>
{
    private HashSet<Key> Keys = new HashSet<Key>();
    private List<Value> Values = new List<Value>();
    public int Count => Keys.Count;
    public Boolean Add(Key key, Value value) {
        int oldCount = Keys.Count;
        Keys.Add(key);
        if (oldCount != Keys.Count) {
            Values.Add(value);
            return true;
        }
        return false;
    }
}

1 个答案:

答案 0 :(得分:3)

是的,当然有可能。请考虑以下片段:

./build/examples/rtpose/rtpose.bin -caffemodel ./model/coco/pose_iter_440000.caffemodel -caffeproto ./model/coco/pose_deploy_linevec.prototxt -camera_resolution "40x30" -camera 0 -resolution "40x30" -start_scale 0.1 -num_scales=0 -no_display true -net_resolution "16x16"

线程1可以执行 if (!map.ContainsKey(fileName)) { map.Add(fileName, new Object()); 并发现映射不包含密钥,因此它将继续添加它,但在它有机会添加它之前,线程2也可以执行{{1}此时它还会发现地图不包含密钥,因此它也会继续添加它。当然,这将失败。

编辑(澄清后)

所以,问题似乎是如何尽可能少地锁定主if (!map.ContainsKey(fileName)),以及如何防止缓存对象被初始化两次。

这是一个复杂的问题,所以我不能给你一个可以运行的现成的答案,(特别是因为我目前还没有方便的C#开发环境),但一般来说,我认为你应该按以下步骤操作:

  1. 使用if (!map.ContainsKey(fileName))完全保护您的map

  2. 尽可能少地锁定map;如果未找到对象在地图中,请将对象添加到地图中并立即退出锁定。这将确保此映射不会成为进入Web服务器的所有请求的争用点。

  3. 在check-if-present-and-add-if-not片段之后,您将保留一个保证在地图中的对象。但是,此对象可能也可能不会在此时初始化。没关系。我们将在下一步处理。

  4. 重复锁定和检查的习惯用法,这次使用缓存的对象:对该特定对象感兴趣的每一个传入请求都需要锁定它,检查它是否已初始化,如果没有,则初始化它。当然,只有第一个请求才会受到初始化的惩罚。此外,在对象完全初始化之前到达的任何请求都必须等待lock(),直到对象初始化为止。但这一切都很好,这正是你想要的。