首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >c#多线程字典-使用新的值集最佳实践刷新活动字典。创建新的Dict,还是逐项重新加载旧的dict?

c#多线程字典-使用新的值集最佳实践刷新活动字典。创建新的Dict,还是逐项重新加载旧的dict?
EN

Stack Overflow用户
提问于 2020-11-06 01:00:12
回答 2查看 189关注 0票数 0

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

锁语句,逐项清除和添加

代码语言:javascript
运行
复制
    lock (myLock){
            myDict.Clear();
            foreach (var itemToAdd in itemsToAdd)
                myDict.Add(config.key, config);
        }

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

代码语言:javascript
运行
复制
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;

做一个直接的交换(包含来自将军的评论)

代码语言:javascript
运行
复制
public ConcurrentDictionary<string, string> myDict = new ConcurrentDictionary<string, string>();;
public void UpdateDictionary(){
     Interlocked.Exchange(ref myDict, LoadDictionary());
}

据我所知,只要您不将引用存储到其他地方(因为您不会得到对ref'd字典的任何更新),最后一个选项就会正常工作。第一个选项将停止任何访问,直到dict被更新(如果过时的值是需要考虑的,则更安全,对于较小的dicts来说应该是安全的),第二个选项避免锁定对象并避免丢失引用,但是在收到更新提示后,您将面临访问旧值的风险。

这是一个格式相当差的问题(对不起),但是是否有执行上述操作的最佳实践/标准?和/或上述代码段中是否有任何主要漏洞?

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

Edit2:误解了interlocked.exchange所做的,仍然改进了我的解决方案

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-11-06 08:02:50

对于选项1,请注意,您不仅必须在写上锁定,而且要在所有读取时锁定,否则锁是无用的。这意味着读取将彼此阻塞,如果沿着这个路由-至少用户ReaderWriterLockSlim而不是简单的锁。

选项2使字典暂时处于不一致的状态(一些值是旧的,一些是新的),这也可能是一个问题,加上ConcurrentDictionary应该在常规字典上有一些开销(大多数情况下它是不相关的,但是谁知道您的特定用例)。

备选方案3应是最快的。请注意,我认为没有必要使用Interlocked.Exchange,您只需分配:

代码语言:javascript
运行
复制
myDict = LoadDictionary();

但是还要注意,为了确保其他线程没有读取某些缓存的值--用volatile标记volatile是有意义的(这有一些奇怪之处,但在特定的用例中应该工作得很好)。

然后,就没有必要在选项3中使用ConcurrentDictionary了,因为您永远不会写到该字典(在它被指定并提供给读者之后)。所以最好让它像ReadOnlyDictionary一样。从多个线程读取是安全的,只要字典从未被修改。

票数 5
EN

Stack Overflow用户

发布于 2020-11-06 09:46:49

这个答案涉及大字典的情况,这些字典几乎不受更改,而且有一些机制只获取数据库中的差异(基于时间戳或其他东西)。在这些情况下,您可以考虑使用ImmutableDictionary而不是普通字典(或并发字典)。与可变字典相比,它的缺点是速度更慢,内存占用也更大,其优点是能够提供非常廉价的数据快照。这是可能的,因为它本身本质上是一个快照,因为它是不可变的。相反,只有通过创建整个集合的副本才能创建可变集合的快照,这可能会导致争用,因为集合在复制操作期间被锁定。

代码语言:javascript
运行
复制
private ImmutableDictionary<string, string> _myDict
    = ImmutableDictionary.Create<string, string>();

public IReadOnlyDictionary<string, string> MyDict
    => Volatile.Read(ref _myDict);

public void UpdateDictionary()
{
    ImmutableInterlocked.Update(ref _myDict, current =>
    {
        // The method below should fetch the changes from the database
        KeyValuePair<string, string>[] changes = GetChangesFromDB();

        var builder = current.ToBuilder();
        foreach (var change in changes)
        {
            if (change.Value == null)
                builder.Remove(change.Key); // Remove
            else
                builder[change.Key] = change.Value; // Add or update
        }
        return builder.ToImmutable();
    });
}

ImmutableDictionary类(RemoveSetItem等)的方法不修改实例。相反,它们返回一个新实例。这是负担得起的,因为它们在内部实现为二叉树,主要由可重用的节点组成。对于更多的修改,使用构建器更有效,如上面的示例所示。

ImmutableInterlocked.Update是一个内置的助手方法,用于原子地更新任何类型的引用类型变量,而不仅仅是不可变的集合:

通过指定的转换函数,使用乐观锁定事务语义突变一个就地的值.为了赢得乐观锁定竞赛的胜利,我们尽可能多地重新尝试这种转换。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/64707448

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档