我有一个C ++ 11程序,它执行一些计算并使用std::unordered_map
来缓存这些计算的结果。该程序使用多个线程,并使用共享unordered_map
来存储和共享计算结果。
基于我对unordered_map
和STL容器规范以及unordered_map thread safety的阅读,似乎多个线程共享的unordered_map
可以一次处理一个线程写入,但一次很多读者。
因此,我正在使用std::mutex
将insert()
调用包装到地图中,因此一次最多只插入一个线程。
但是,我的find()
调用没有互斥锁,因为从我的阅读中看,许多线程似乎应该能够立即读取。但是,我偶尔会得到数据竞赛(由TSAN检测到),在SEGV中表现出来。数据竞争明确指向我上面提到的insert()
和find()
来电。
当我将find()
调用包装在互斥锁中时,问题就会消失。但是,我不想序列化并发读取,因为我试图尽可能快地使这个程序。 (仅供参考:我正在使用gcc 5.4。)
为什么会这样?我对std::unordered_map
的并发保证的理解不正确吗?
答案 0 :(得分:5)
您仍然需要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';
}
答案 1 :(得分:2)
这种方法存在一些问题。
首先,std::unordered_map
有两个find
重载 - 一个是const
,另一个不是。
我敢说我不相信非{const}版本的find
会改变地图,但是对于从多线程调用非const方法的编译器来说仍然是数据竞争,而一些编译器实际上使用未定义的行为进行讨厌的优化。
首先 - 你需要确保当多个线程调用std::unordered_map::find
时,他们使用const版本。这可以通过使用const引用引用地图然后从那里调用find
来实现。
第二,你错过了许多线程可能会在你的地图上调用const查找的部分,但是其他线程无法在对象上调用非const方法!我可以想象很多线程同时调用find
和一些调用insert
,导致数据竞争。想象一下,例如,insert
使地图的内部缓冲区重新分配,而其他一些线程迭代它以找到想要的对。
解决方案是使用具有独占/共享锁定模式的C ++ 14 shared_mutex
。当线程调用find
时,它会锁定共享模式的锁,当线程调用insert
时,它会将其锁定在独占锁上。
如果您的编译器不支持shared_mutex
,您可以使用特定于平台的同步对象,例如Linux上的pthread_rwlock_t
和Windows上的SRWLock
。
另一种可能性是使用无锁散列图,就像英特尔的线程构建块库提供的一样,或者在MSVC并发运行时使用concurrent_map
。实现本身使用无锁算法,确保访问始终是线程安全的,同时快速。