我应该使用哪个集合来从多个线程读取元素并定期完全覆盖集合?



我将使用一个静态集合,它将被核心进程用于读取,并由后台服务每隔X分钟完全更新一次。

后台进程将每隔X分钟从数据库加载更新的数据,并将接收到的数据集设置到这个静态集合中。

核心进程将接收许多任务来检查这个集合中是否存在某些值。每个任务将在单独的线程中处理。会有很多请求,它应该是非常快的,所以我不能要求数据库的每个请求,我需要一个可更新的列表在内存中。

public class LoadedData
{
public static HashSet<string> Keys { get; set; }
}
public class CoreProcess
{
public bool ElementExists(string key)
{
return LoadedData.Keys.Contains(key);
}
}
public class BackgroundProcess
{
public async Task LoadData()
{
while (true)
{
LoadedData.Keys = GetKeysFromDb();
await Task.Delay(TimeSpan.FromMinutes(5));
}
}
}

所以,我正在寻找这个问题的最佳解决方案。我正在考虑使用HashSet<T>,因为我确信集合中的每个元素都是唯一的。但是HashSet<T>不是线程安全的。所以我开始考虑BlockingCollection<T>,ConcurrentBag<T>,ConcurrentDictionary<T, byte>,但后来我想知道我是否需要一个线程安全的收集在这里。看起来不是,因为我不打算添加/更新/删除集合中的特定元素。只能从数据库完全重写。

  1. 那么,这是否意味着我可以使用简单的HashSet<T>?

  2. 你会用哪个集合来解决这个问题?

  3. 一般来说,如果核心进程同时读取,而后台进程完全覆盖收集,会有什么问题吗?

因此,一旦HashSet<string>成为LoadedData.Keys属性的值,它就有效地不可变了。在这种情况下,您的代码几乎没问题。唯一缺少的部分是确保该属性对所有相关线程可见。

理论上,编译器或抖动器可能会使用该属性的缓存/过期值,而不是查看当前存储在主存中的内容。在实践中,您可能永远不会遇到这种现象,但是如果您想按照规则行事,您必须使用volatile语义读写该属性。如果Keys是一个字段,则可以使用volatile关键字对其进行修饰。因为它是一个属性,你必须做更多的工作:

public class LoadedData
{
private volatile static HashSet<string> _keys;
public static HashSet<string> Keys
{
get => _keys;
set => _keys = value;
}
}

…或者使用Volatile类而不是volatile关键字:

public class LoadedData
{
private static HashSet<string> _keys;
public static HashSet<string> Keys
{
get => Volatile.Read(ref _keys);
set => Volatile.Write(ref _keys, value);
}
}

最后一个警告:HashSet<string>的不变性不是由编译器强制的。这只是你与未来的自己,以及你代码的其他维护者之间的口头契约。如果一些变化的代码进入了您的代码库,那么您的程序的行为将成为正式未定义的。如果您想防止这种情况发生,那么在语义上最正确的方法是用ImmutableHashSet<string>替换HashSet<string>。不可变集合比可变集合慢得多(通常至少慢10倍),所以这是一种权衡。你可以拥有平和的心态,也可以拥有极致的表现,但不能两者兼得。

最新更新