使用std :: unordered_map进行数据竞争,尽管使用互斥锁进行锁定插入

时间:2016-06-27 18:44:27

标签: c++ concurrency stl containers unordered-map

我有一个C ++ 11程序,它执行一些计算并使用std::unordered_map来缓存这些计算的结果。该程序使用多个线程,并使用共享unordered_map来存储和共享计算结果。

基于我对unordered_map和STL容器规范以及unordered_map thread safety的阅读,似乎多个线程共享的unordered_map可以一次处理一个线程写入,但一次很多读者。

因此,我正在使用std::mutexinsert()调用包装到地图中,因此一次最多只插入一个线程。

但是,我的find()调用没有互斥锁,因为从我的阅读中看,许多线程似乎应该能够立即读取。但是,我偶尔会得到数据竞赛(由TSAN检测到),在SEGV中表现出来。数据竞争明确指向我上面提到的insert()find()来电。

当我将find()调用包装在互斥锁中时,问题就会消失。但是,我不想序列化并发读取,因为我试图尽可能快地使这个程序。 (仅供参考:我正在使用gcc 5.4。)

为什么会这样?我对std::unordered_map的并发保证的理解不正确吗?

2 个答案:

答案 0 :(得分:5)

您仍然需要mutex让读者保留作者,但您需要共享C++14std::shared_timed_mutex,您可以使用范围锁定std::unique_lockstd::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。实现本身使用无锁算法,确保访问始终是线程安全的,同时快速。