使用 MemoryBarrier 在 Dictionary 多线程应用程序中传播更改
本文关键字:应用程序 多线程 传播 MemoryBarrier Dictionary int 使用 | 更新日期: 2023-09-27 18:30:45
我有一个 .NET 3.5 应用程序,其中字典实例在两个线程之间共享。我知道字典本身在任何方面都不是线程安全的,但我只有一个线程可以修改字典,另一个线程只需要确保它在执行工作时具有最新值。(即使是最新的也不是严格意义上的硬性要求)
一个线程正在接收间歇性串行数据并调用 Set 函数来更改字典中的值。(这个字典在初始设置后是一个固定的大小,我基本上只是把它作为一个稀疏数组)
第二个线程收集当前存储在字典中的值,并通过 GetLatestValues() 对它们进行一些处理。
public class HwMemoryMap{
Dictionary<int, HwDataItem> HwCache;
public void Set(HwDataItem dataItem){
HwCache[dataItem.PtId] = dataItem;
MemoryBarrier();
}
public List<HwDataItem> GetLatestValues(){
System.Threading.Thread.MemoryBarrier();
List<HwDataItem> HwDataItemList = new List<HwDataItem>();
// do work here to pull appropriate values out of HwCache
HwDataItemList.Add(HwCache[0]); // etc
return HwDataItemList;
}
}
这里的 MemoryBarrier() 调用是否足以确保对特定键的字典值的更改在所有线程/内核中传播?
我的测试没有发现任何问题,但鉴于这些问题的性质,这并不能给我带来任何安慰。
不,这不安全。您仍然冒着同时写入和读取数据结构的风险。您可以使用一个技巧,如果写入足够频繁,效果很好。基本上,您要确保HwCache
引用的数据结构保持不可变。每次要更改数据结构时,首先将其复制到新实例中,然后在独占锁中更改新实例。然后,完成更改后,将HwCache
引用换出新实例。要使其正常工作,您必须HwCache
标记为 volatile
.
public class HwMemoryMap
{
private object lockobj = new object();
volatile Dictionary<int, HwDataItem> HwCache;
public void Set(HwDataItem dataItem)
{
lock (lockobj)
{
var copy = new Dictionary<int, HWDataItem>(HwCache);
copy[dataItem.PtId] = dataItem;
HwCache = copy;
}
}
public List<HwDataItem> GetLatestValues()
{
var local = HwCache;
var HwDataItemList = new List<HwDataItem>();
// do work here to pull appropriate values out of local
HwDataItemList.Add(local[0]); // etc
return HwDataItemList;
}
}
我对 C# 不是很熟悉,但我不会对"引擎盖下"发生的事情做出假设。 特别是,如果插入代码假定它是唯一访问数据结构的代码,则在分配空间或其他方式时,它可能会留下部分更新的引用。
请考虑以下(简化)代码:
Insert(TKey key, TVal val) {
if (this.size > this._threshold) {
// Allocate more space and
// move to a new table
}
// Find location and insert
}
如果所有这些都发生在您的一个存储中(我不是说是,我不知道 C# 如何实现字典),那么如果任何中间状态传播到不同的线程,内存屏障不会保存您。
可能更有意义的是一对读/写器锁。 如果常见的情况是读取,则可以让多个线程都抓住读取器锁,并且只有在需要更新时才需要授予对写入线程的独占访问权限。