C# 多线程字典 - 使用新的一组值最佳实践刷新实时字典。创建新的字典,还是逐项重新加载旧的字典?

The*_*mon 1 c# multithreading dictionary thread-safety

我有一个复杂对象的字典,只要数据库及其源数据发生更改,就需要更新该字典。条目可能已更改、添加或删除,因此本质上我们需要重新创建/重新加载整个词典。据我所知,有几种不同的方法可以做到这一点,我想知道是否有最佳实践?(或者如果我对比较的理解是正确的)

锁定语句,逐项清除和添加

    lock (myLock){
            myDict.Clear();
            foreach (var itemToAdd in itemsToAdd)
                myDict.Add(config.key, config);
        }
Run Code Online (Sandbox Code Playgroud)

逐项添加到字典中,然后删除不应该存在的内容

var tmpDict = LoadDictionary();
foreach(var key in myDict.Keys.ToList()) //check for deleted keys
    if(!tmpDict.ContainsKey(key))
          myDict.TryRemove(key, out _);
foreach(var pair in tmpDict) //update or add every key
    myDict[pair.Key] = pair.Value;
Run Code Online (Sandbox Code Playgroud)

进行直接交换(合并将军的评论)

public ConcurrentDictionary<string, string> myDict = new ConcurrentDictionary<string, string>();;
public void UpdateDictionary(){
     Interlocked.Exchange(ref myDict, LoadDictionary());
}
Run Code Online (Sandbox Code Playgroud)

据我所知,只要您从不在其他地方存储对字典的引用(因为您不会获得对引用字典的任何更新),最后一个选项就应该可以正常工作。第一个选项将停止任何访问,直到更新字典(如果担心过时的值,则更安全,并且对于较小的字典来说应该没问题),第二个选项避免锁定对象并避免丢失引用,但运行您收到更新提示后几毫秒/秒内访问旧值的风险

这是一个格式相当糟糕的问题(抱歉),但是是否有执行上述操作的最佳实践/标准?和/或上述代码片段中是否存在重大漏洞?

编辑:在我的用例中,我们有几个独立的服务正在运行,数据库将被其中一个服务更改,所以我们不能先更新字典,但有点过时也没什么大不了的。

Edit2:误解了 interlocked.exchange 的作用,但仍然改进了我的解决方案

Evk*_*Evk 5

对于选项 1,请注意,您不仅必须在写入时锁定,而且还必须在所有读取时锁定,否则锁定是无用的。这意味着读取将互相阻塞,如果走这条路——至少是用户ReaderWriterLockSlim而不是简单的锁。

选项 2 使字典在短时间内处于不一致的状态(有些值是旧的,有些是新的),这也可能是一个问题,而且ConcurrentDictionary应该比常规字典有一些开销(大多数时候它是无关紧要的,但谁知道你的具体情况)用例)。

选项 3 应该是最快的。请注意,我认为不需要使用Interlocked.Exchange,您可以只分配:

myDict = LoadDictionary();
Run Code Online (Sandbox Code Playgroud)

但还要注意,为了确保其他线程不会读取某些缓存的值 - 用 进行标记是有意义的myDictvolatile这有一些怪癖,但在您的特定用例中应该可以正常工作)。

然后,就不需要ConcurrentDictionary在选项 3 中使用,因为您永远不会写入该字典(在它被分配并可供读者使用之后)。所以最好把它做成类似的东西ReadOnlyDictionary。只要该字典从未被修改,从多个线程读取就是安全的。

  • 我喜欢这个答案,我可能会补充一点,虽然“Interlocked.Exchange”不仅仅是关于原子性,它还关于内存可见性+1,尽管引用是原子的,但它们仍然可以被缓存,操作员可能会或可能不关心这一点 (2认同)