如何以线程安全的方式使用`std::unordered_map`?
Posted
技术标签:
【中文标题】如何以线程安全的方式使用`std::unordered_map`?【英文标题】:How to use `std::unordered_map` in a thread-safe way? 【发布时间】:2021-07-12 19:07:37 【问题描述】:我做了什么
我的想法是包装std::unordered_map
,并为每个人锁定它
手术。它看起来像这样(我将只留下一个操作
为了不使问题混乱):
template<class Key, class T>
class thread_safe_unordered_map
public:
T &operator[](const Key &key)
std::lock_guard<std::mutex> lockm_mutex;
return m_underlying[key];
private:
std::unordered_map<Key, T> m_underlying;
std::mutex m_mutex;
;
但是后来我想到了,现在我不知道如何正确地做到这一点。
想象一下这种情况。
线程 1: 调用 operator[] 进行写入线程 2: 调用 operator[] 进行读取
两者都各司其职。现在,因为 operator[] 返回一个引用,并且它们都 将根据相同的参考采取行动,我认为这仍然可能是一场数据竞赛。 是这样吗?
我现在的想法
因为this:
所有的 const 成员函数都可以被不同的线程同时调用 同一个容器。此外,成员函数 begin()、end()、 rbegin()、rend()、front()、back()、data()、find()、lower_bound()、 upper_bound()、equal_range()、at() 以及,除了在关联容器中, operator[],出于线程安全的目的,表现为 const。
我有这个问题。在线程上使用at()
是否正确,并且
被锁定的是映射值?
【问题讨论】:
返回非常量引用与封装相反。你是对的,锁无助于保护对同一内存的并发读写导致数据竞争 更糟糕的是,新获取的引用可能随时变成悬空引用,而不会发出警告。如果幸运的话,您将能够对撞击坑进行一些取证调试,以找出问题所在。 您是否曾经从多线程区域的地图中删除元素?如果没有,你应该没问题。一旦一个元素被插入到一个无序映射中,它的地址应该保持不变。无论如何,这种方法效率低下。有外部库提供的并发哈希表,例如 Intel TBB 或 Facebook Folly (IIRC)。此外,正如所指出的,元素的更新可能会导致数据竞争。 不,没有被删除。有些是在开始时插入的,之后只有一个线程可以修改现有元素,其余的只能从中读取。谢谢你的建议,我会看看你告诉我的图书馆。 @DanielLangr 锁定每个操作通常是不够的,因为很多时候您想要原子地执行多个操作(例如,删除一个条目然后插入另一个条目,而没有任何其他线程有可能看到这两个操作之间的中间状态)。因此,更好的方法是将锁定排除在数据结构之外,并在更高级别完成,代码完全了解它试图以原子方式完成的任务。 【参考方案1】:现在,因为 operator[] 返回一个引用,并且它们都将作用于同一个引用,我认为这仍然可能是数据竞争。是这样吗?
这是正确的假设 T
不是线程安全类型。
这不是容器操作中的数据竞争,而是T
类型对象的使用中的数据竞争。
我有这个问题。在线程上使用 at() 是否正确
如果你只调用 const 限定的at
,那么你永远不会得到一个非常量引用,因此不能修改任何元素的值。如果您不修改这些值,那么您就没有数据竞争。
如果您使用非 const 限定 at
,那么在所描述的数据竞争方面没有区别。
附:您会发现您将无法拥有任何同步的非常量成员函数,因为锁定互斥体需要修改。您可以使用mutable
解决此问题。
【讨论】:
以上是关于如何以线程安全的方式使用`std::unordered_map`?的主要内容,如果未能解决你的问题,请参考以下文章
如何以线程安全的方式使用`std::unordered_map`?
如何以编程方式证明 StringBuilder 不是线程安全的?