多线程操作Dictionary为何出现键不存在而非重复添加错误?
多线程下Dictionary操作的异常问题
我编写了一个供项目多线程调用、向Dictionary中添加值的简单方法:
public void AddValue(int kid, int vid) { if(!dic.ContainsKey(kid)) dic.Add(kid, new List<int>()); dic[kid].Add(vid); }
运行代码时,有时会出现特定键ID不存在的错误,我认为这是多线程竞争导致的。但理论上,多线程竞争应该集中在if(!dic.ContainsKey(kid)) dic.Add(kid, new List<int>());这段代码,所有未初始化对应键的线程都会满足if条件并尝试添加键,错误应该是“字典中已存在该键”才对。为何会出现通过if检查后键仍未初始化的情况?
注:我了解AutoResetEvent,也能实现无错误运行,但我不理解该if语句为何会出现这种“失效”情况。
问题解析
这是因为Dictionary本身并非线程安全集合,它的内部哈希表结构在多线程并发读写、修改时会被破坏,触发的异常不止你预期的“重复键”这一种,还会出现逻辑上看似矛盾的异常。
具体来说:
- 当线程A执行完
!dic.ContainsKey(kid)判断为真,正要执行dic.Add(kid, new List<int>())时,线程B可能正在修改Dictionary的内部结构——比如哈希表扩容、重新排列条目,这会打断线程A的Add操作,导致键值对没有被正确写入到集合中; - 也有可能线程A的Add操作执行了一部分,但因为并发导致Dictionary的内部索引、哈希映射出现混乱,后续执行
dic[kid].Add(vid)时,无法通过键找到对应的列表。
本质上,ContainsKey和Add是两个独立的非原子操作,在无同步保护的并发场景下,非线程安全集合的行为是未定义的——它可能抛出任何异常,甚至直接导致程序崩溃,你遇到的“通过检查却找不到键”就是这种未定义行为的一种表现。
内容的提问来源于stack exchange,提问作者Piggy Chu
相关产品推荐
相关产品推荐

