c# 多线程字典 - 使用一组新的值最佳实践刷新实时字典。创建新的字典,还是逐项重新加载旧的字典? [关闭]
Posted
技术标签:
【中文标题】c# 多线程字典 - 使用一组新的值最佳实践刷新实时字典。创建新的字典,还是逐项重新加载旧的字典? [关闭]【英文标题】:c# multithreaded dictionary - refresh live dictionary with new set of values best practices. Create new Dict, or reload old dict item by item? [closed] 【发布时间】:2021-02-18 18:19:53 【问题描述】:我有一个复杂对象字典,只要数据库及其源数据发生更改,就需要更新该字典。条目可能已更改、添加或删除,因此基本上我们需要重新创建/重新加载整个字典。据我所知,有几种不同的方法可以做到这一点,我想知道是否有最佳实践? (或者如果我对比较的理解是正确的)
锁定语句,逐项清除和添加
lock (myLock)
myDict.Clear();
foreach (var itemToAdd in itemsToAdd)
myDict.Add(config.key, config);
逐项添加到字典中,然后删除不应该存在的任何内容
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;
进行直接交换(结合将军的评论)
public ConcurrentDictionary<string, string> myDict = new ConcurrentDictionary<string, string>();;
public void UpdateDictionary()
Interlocked.Exchange(ref myDict, LoadDictionary());
据我所知,只要您从不在其他地方存储对字典的引用(因为您不会对引用的字典进行任何更新),最后一个选项应该可以正常工作。第一个将停止任何访问,直到字典被更新(如果过时的值是一个问题更安全,并且对于较小的字典应该没问题),第二个选项避免锁定对象并避免丢失引用,但运行你收到更新提示后访问旧值几毫秒/秒的风险
这是一个格式相当糟糕的问题(抱歉),但是否有执行上述操作的最佳实践/标准?和/或上述代码 sn-ps 中是否有任何重大漏洞?
编辑:在我的用例中,我们有几个独立的服务在运行,数据库将被其中一个更改,所以我们不能先更新字典,但有点过时并不是什么大问题要么交易。
Edit2:误解了 interlocked.exchange 的作用,但仍然改进了我的解决方案
【问题讨论】:
您是否关心(访问字典的人)在数据更改时是否有可能出现陈旧值? 如果您不关心读取过时值的机会,只需替换引用或使用Interlocked.Exchange(ref _dict, newDictionary);
您尚未披露与数据库数据如何变化相关的重要信息。是通过同一个应用程序吗?您可以先更新内存字典,然后启动更新数据库的任务吗?只有在确定了流程、容差和后备等情况后,我们才能得出合适的解决方案。
请问您为什么要这样做?如果此字典要保留某种审计功能并且您正在使用实体框架,则可以覆盖 OnSaveChanges()
“第二个选项 [...] 会让您面临访问旧值的风险” itemsToAdd 之后 锁定了字典,因此在此获取过程中字典是可访问的并且包含旧值。而且从数据库中获取数据应该比更新内存中的集合更耗时。
【参考方案1】:
使用选项 1,请注意,您不仅要锁定写入,还要锁定所有读取,否则锁定是无用的。这意味着读取将相互阻塞,如果走这条路 - 至少用户 ReaderWriterLockSlim
而不是简单的锁定。
选项 2 让字典暂时处于不一致的状态(一些值是旧的,一些是新的),这也可能是一个问题,加上 ConcurrentDictionary
应该比常规字典有一些开销(大多数时候它是无关紧要的,但谁知道你的具体用例)。
选项 3 应该是最快的。请注意,我认为没有必要使用Interlocked.Exchange
,您可以分配:
myDict = LoadDictionary();
但还要注意,为了确保其他线程不会读取某些缓存值 - 将 myDict
标记为 volatile
是有意义的(这有一些怪癖,但在您的特定用例中应该可以正常工作)。
那么,没有必要在选项 3 中使用ConcurrentDictionary
,因为您永远不会写入该字典(在它被分配并可供读者使用之后)。所以最好把它写成ReadOnlyDictionary
。只要字典不被修改,从多个线程读取是安全的。
【讨论】:
我喜欢这个答案,尽管Interlocked.Exchange
不仅仅是关于原子性,它关于内存可见性+1,尽管引用是原子的,但它们仍然可以被缓存,操作可能或可能不在乎
是的,但我们希望通过使字段 volatile 来涵盖这一点。
啊,是的。我知道你要去哪里【参考方案2】:
此答案涉及大型字典的情况,这些字典很少且不经常更改,并且有一些机制可以仅从数据库中获取差异(基于时间戳或其他内容)。在这些情况下,您可以考虑使用ImmutableDictionary
而不是普通字典(或并发字典)。与可变字典相比,它的缺点是速度较慢且内存占用较大,其优点是能够提供非常便宜的数据快照。这是可能的,因为它本身本质上是一个快照,因为它是不可变的。相反,只能通过创建整个集合的副本来创建可变集合的快照,这可能会导致争用,因为在复制操作期间集合被锁定。
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
类(Remove
、SetItem
等)的方法不会修改实例。相反,它们返回一个新实例。这是负担得起的,因为它们在内部实现为二叉树,主要由可重用的节点组成。对于不止少量的修改,使用builder 会更有效,如上例所示。
ImmutableInterlocked.Update
是一个内置的辅助方法,用于自动更新任何类型的引用类型变量,而不仅仅是不可变集合:
通过指定的转换函数使用乐观锁定事务语义就地改变值。转换会根据需要重试多次,以赢得乐观锁定竞赛。
【讨论】:
以上是关于c# 多线程字典 - 使用一组新的值最佳实践刷新实时字典。创建新的字典,还是逐项重新加载旧的字典? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章
excel2003中一组新的数据怎么覆盖新的数据?求解 不清楚的还可再问
Canonical 近日发布了一组新的 Linux 内核安全更新