C++无锁实现哲学家就餐时unordered_map大小异常问题求助
问题原因分析
1. 对std::unordered_map的线程安全特性存在误解
std::unordered_map是非线程安全容器,C++标准明确规定:多个线程同时调用容器的非const成员函数(包括emplace、operator[]等)属于未定义行为,和是否提前调用reserve预留空间无关:
reserve仅能保证容器存储的元素数量达到阈值前不会触发rehash、不会发生已存元素的地址重定位,但不会改变容器内部元数据(元素计数、桶链表指针、哈希表状态位等)的非原子性。- 多线程并发调用
emplace时,内部元数据会出现数据竞争:比如元素计数变量并发写导致计数错误、桶链表节点并发插入导致链表结构损坏(出现重复节点、野指针、环形链表等),最终表现为容器实际遍历得到的元素数量远大于预期。
2. 读写操作的隐含竞争
「不同线程操作独立内存地址」的假设不成立:
- 不管是
operator[]还是at,在访问元素前都需要先遍历桶链表查找对应key的节点,只要此时有其他线程在执行插入操作修改桶结构,查找过程就会访问到损坏的内存结构,触发崩溃、读取到错误值等异常。 - 即使所有插入操作完成后再并发修改元素,当前对tuple的赋值操作也没有原子性保障,极端情况下会出现读写撕裂。
3. 设计逻辑冗余
实验中的哲学家ID是从1到N连续生成的,完全不需要使用unordered_map存储状态,用数组/vector即可完全规避哈希表的并发问题。
修复方案
- 若要保留无锁设计:将
umap替换为长度为N+1的数组,数组元素存储带原子性的统计值,每个线程仅操作自己ID对应的下标元素,全程无竞争,性能远高于哈希表方案。 - 若要继续使用
unordered_map:- 主线程提前插入所有ID对应的元素,子线程运行过程中不执行插入/删除操作。
- 给所有哈希表操作加互斥锁,或者替换为成熟的第三方无锁哈希表实现。
- 统计值改用原子变量存储,避免读写撕裂。
内容的提问来源于stack exchange,提问作者kishoredbn
相关产品推荐
相关产品推荐

