Dictionary<TKey, TValue> 的线程安全
Posted
技术标签:
【中文标题】Dictionary<TKey, TValue> 的线程安全【英文标题】:Thread safety of a Dictionary<TKey, TValue> 【发布时间】:2011-01-03 20:34:04 【问题描述】:如果我初始化一个通用字典一次,并且不允许进一步的添加/更新/删除,那么在没有锁定的情况下让多个线程读取它是否安全(假设字典在读取器启动之前被初始化)?
非泛型 HashTable 的帮助中有一条注释说它对多个读者是安全的,但我没有看到泛型字典的类似内容。
【问题讨论】:
【参考方案1】:供您将来参考,文档在这里:
http://msdn.microsoft.com/en-us/library/xfhwa508.aspx
上面写着:
字典 可以支持多个阅读器 同时,只要 集合未修改。即使是这样, 通过集合枚举是 本质上不是线程安全的 程序。在极少数情况下 枚举与写竞争 访问,集合必须是 在整个枚举期间锁定。 允许访问集合 通过多个线程进行阅读和 写,你必须实现你自己的 同步。
【讨论】:
只是想确保我正确理解引用段落的“即使如此......”部分。是否真的:(1)如果字典不再被修改(如OP的问题中所述),那么枚举也不会成为问题。 (2) 如果字典将来可能会被修改,那么即使ConcurrentDictionary
枚举也是“本质上不是线程安全的过程”。对吗?
@RayLuo:一般情况下,无论是多线程还是单线程,都不允许在枚举“进行中”时修改集合。
并发框架集合(例如,System.Collections.Concurrent.ConcurrentDictionary
)通常允许在枚举期间进行修改。但是,他们通过制作集合的副本并让您枚举副本来实现此目的。并且在制作副本时整个收藏都被锁定。框架工程师可能会争辩说,这意味着您没有在枚举“飞行”时修改集合(因为在引擎盖下,整个事情在枚举“飞行”时被锁定),但从框架用户的角度来看,枚举正在进行中。
感谢 Eric 和@Brian 的洞察力。对于它的价值,这里引用Dictionary.GetEnumerator():“只要集合保持不变,枚举器就保持有效。如果对集合进行更改,例如添加、修改或删除元素,枚举器将不可恢复地失效并且它的行为是未定义的。枚举器没有对集合的独占访问权;" ConcurrentDictionary 枚举 differently,顺便说一句。【参考方案2】:
是的,如果您不再修改字典是安全的。线程安全只是读/写场景中的问题
【讨论】:
不过,您必须考虑内部状态。在外部,您可能只从字典中读取一个值。但是,您不知道在检索期间可能会发生哪些状态转换。 OP @JMarsch 在上面发表评论并获得 10 票赞成。那么“内部状态”会如何影响 OP 的场景呢?If I initialize a generic dictionary once, and no further adds/updates/removes are allowed, is it safe to have multiple threads reading from it with no locking (assuming that the dictionary is initialized before the readers are started)?
还可以吗
对于字典,是的,Eric Lippert 的答案是正确的。从我的评论中要记住的重要一点是,通常,如果您只对其进行读取,则假设数据结构是线程安全的是不够的,因为您不知道读取如何影响内部状态。就字典而言,事实证明,读取操作确实是安全的,只要在并发读取器使用它时没有人写入它。所以在你的例子中,你初始化一次,然后只读取它,你是安全的。
感谢您的澄清。因此,从您的第一条评论中可采取的行动是“理想情况下,仔细检查数据结构的文档或源代码,以查看读取是否是线程安全的”,因为直觉上人们会假设读取是线程安全的,所以当事实并非如此,数据结构开发人员希望将其记录下来。 (为了完整起见,我做了一个脑力练习,并想出了一个可以根据读取频率动态调整其结构的霍夫曼树,可能不是线程安全的读取。也许这 10 位支持者也做了他们的练习。)跨度>
以上是关于Dictionary<TKey, TValue> 的线程安全的主要内容,如果未能解决你的问题,请参考以下文章
为啥 Dictionary<TKey, TValue> 上的这个 Linq 查询不能作为数据源工作
Dictionary<TKey, TValue> 的线程安全
我可以为 Dictionary<TKey, TValue> 条目使用集合初始化程序吗?