使用std::unordered_map进行数据竞争,尽管使用互斥锁插入
Data race with std::unordered_map, despite locking insertions with mutex
我有一个c++ 11程序,它执行一些计算并使用std::unordered_map
来缓存这些计算的结果。该程序使用多个线程,它们使用一个共享的unordered_map
来存储和共享计算结果。
根据我对unordered_map
和STL容器规范的阅读,以及unordered_map线程安全,似乎由多个线程共享的unordered_map
可以一次处理一个线程写入,但一次处理许多读者。
因此,我使用std::mutex
来包装我的insert()
调用映射,这样一次最多只有一个线程插入。
然而,我的find()
调用没有互斥锁,从我的阅读,似乎许多线程应该能够一次读取。然而,我偶尔会得到数据竞争(如TSAN检测到的),在SEGV中表现出来。数据竞争清楚地指向我上面提到的insert()
和find()
调用。
当我将find()
调用包装在互斥锁中时,问题就消失了。然而,我不想序列化并发读取,因为我试图使这个程序尽可能快。(仅供参考:我正在使用gcc 5.4运行)
为什么会发生这种情况?我对std::unordered_map
的并发性保证的理解是否不正确?
您仍然需要一个mutex
供您的读者将作者拒之门外,但是您需要一个共享的。C++14
有一个std::shared_timed_mutex,您可以像这样与作用域锁std::unique_lock和std::shared_lock一起使用:
using mutex_type = std::shared_timed_mutex;
using read_only_lock = std::shared_lock<mutex_type>;
using updatable_lock = std::unique_lock<mutex_type>;
mutex_type mtx;
std::unordered_map<int, std::string> m;
// code to update map
{
updatable_lock lock(mtx);
m[1] = "one";
}
// code to read from map
{
read_only_lock lock(mtx);
std::cout << m[1] << 'n';
}
这种方法有几个问题。
首先,std::unordered_map
对find
有两个重载——一个是const
,一个不是。
我敢说,我不相信find
的非const版本会改变映射,但对于编译器来说,从多线程调用非const方法仍然是一种数据竞争,一些编译器实际上使用未定义的行为来进行糟糕的优化。
因此,第一件事—您需要确保当多个线程调用std::unordered_map::find
时,它们使用const版本。这可以通过使用const引用引用map,然后从那里调用find
来实现。
第二,你忽略了许多线程可以调用你的map上的const find,但其他线程不能调用对象上的非const方法的部分!我完全可以想象许多线程同时调用find
和一些线程同时调用insert
,从而导致数据竞争。想象一下,例如,insert
使map的内部缓冲区重新分配,而其他线程迭代它以找到想要的对。
解决方案是使用c++ 14 shared_mutex
,它具有独占/共享锁定模式。当线程调用find
时,它将锁锁定在共享模式,当线程调用insert
时,它将锁锁定在独占模式。
如果你的编译器不支持shared_mutex
,你可以使用平台特定的同步对象,如Linux上的pthread_rwlock_t
和Windows上的SRWLock
。
另一种可能性是使用无锁哈希映射,就像Intel的线程构建块库或MSVC并发运行时上的concurrent_map
提供的那样。实现本身使用无锁算法,确保访问始终是线程安全的,同时也是快速的。
- 使用C++库在Android项目中修改gradle中的cmake参数,用于插入指令的测试
- 有关插入适配器的错误。[错误]请求从 'back_insert_iterator<vector<>>' 类型转换为非标量类型
- 预处理器:插入结构名称中的前一个行号
- 在未初始化映射的情况下,将值插入到映射的映射中
- 如何在c++中只将键插入到bimap的一侧
- 如何将结构插入到集合中并打印集合的成员
- C++json插入数组
- Visual Studio 2019:插入多个C++风格的单行注释
- nlohmann-json将一个数组插入到另一个数组中
- 有效地使用std::unordered_map来插入或增加键的值
- 为字符串中每 N 个字符插入空格的函数没有按照我认为的方式工作?
- 正在插入动态数组
- 插入或删除时获取usb的dos_name
- 叮叮当当在修复时插入多个"覆盖"说明符
- 链表c++插入,所有情况都已检查,但没有任何工作
- 将重物插入std::map
- C++17 - 使用自定义分配器的节点提取/重新插入 - 适用于 clang++/libc++,但不适用于 libstd
- 在数字之间插入 + 或 - 符号以使其等于整数
- 在字符串中插入空格
- 使用std::unordered_map进行数据竞争,尽管使用互斥锁插入