std::map 上可能的线程不安全操作

Posted

技术标签:

【中文标题】std::map 上可能的线程不安全操作【英文标题】:Possible thread unsafe operation on std::map 【发布时间】:2015-12-15 13:14:45 【问题描述】:

STL 不保证其集合的线程安全。但我想知道以下代码是否真的有效。

一个线程通过调用 [] 运算符在映射 m 上调用非常量操作,但使用映射上已经存在的键。据我所知,这只是调用 find() 并返回对 gcc 中迭代器的引用。另一个线程同时持有 m 的 const 迭代器。

问题是:断言会失败吗?

void doBracket(std::map<int, int>& m) 
  const auto&  val = m[0];
  std::cerr << val << std::endl;



void doIter(const std::map<int, int>& m)
  auto zeroIter = m.find(0);
  auto oneIter = m.find(1);
  auto twoIter = m.find(2);
  assert(zeroIter->second == 0);
  assert(oneIter->second == 1);
  assert(twoIter->second == 2);


int main() 
  std::map<int, int> m = 0, 0, 1, 1, 2, 2;
  std::thread mutateThread doBracket, std::ref(m);
  std::thread constThread  doIter, std:ref(m);
  mutateThread.join();
  constThread.join();

这是 stl_map 的作用:

   operator[](const key_type& __k)
      
        // concept requirements
        __glibcxx_function_requires(_DefaultConstructibleConcept<mapped_type>)

        iterator __i = lower_bound(__k);
        // __i->first is greater than or equivalent to __k.
        if (__i == end() || key_comp()(__k, (*__i).first))
                 //handle this case

        return (*__i).second;
      

【问题讨论】:

无论如何,您的代码都不是线程安全的。每个 ìtererator 更改操作都必须由互斥锁保护。 Dieter:我知道标准是这么说的。我对实际的实现更感兴趣。我更新了 stl_map 代码,这似乎暗示该代码实际上是线程安全的。 【参考方案1】:

如果你查看http://www.cplusplus.com/reference/map/map/operator%5B%5D/,它会说关于 map::operator[]:

迭代器有效性

没有变化。

数据竞赛

容器被访问,并且可能被修改。 该函数访问一个元素并返回一个可用于修改其映射值的引用。同时访问其他元素是安全的。 如果该函数插入一个新元素,那么在容器中同时迭代范围是不安全的。

根据这一点,唯一可能不安全的情况是“如果函数插入新元素,则同时迭代容器中的范围”。由于您没有进行任何插入,因此结论一定是您的代码是安全的。

【讨论】:

【参考方案2】:

您似乎已经确定了一个可以通过了解实现来超越标准的地方。但是,这种方法与使用find() 而不是operator[]() 的保证安全方法相比没有优势。

侧边栏:我个人觉得std::map::operator[]() 几乎没用。我总是想使用find()insert() 或其他东西。

【讨论】:

谢谢,约翰。实际上,我正在调试一种情况,我们恰好遇到这种情况(有两个线程,一个在映射上迭代,一个在已知键上执行括号运算符)。该地图似乎已损坏,并且认为可能是非常量括号运算符导致损坏。从实现来看,情况似乎并非如此。我同意应该将代码更改为使用 find()。但我想排除这种可能性。 嗯,你应该把代码改成使用find(),看看能不能解决问题。如果您使用的是 Linux,您还应该考虑使用 valgrind 和 helgrind 等工具来加快故障排除速度。

以上是关于std::map 上可能的线程不安全操作的主要内容,如果未能解决你的问题,请参考以下文章

如何以线程安全的方式使用`std::unordered_map`?

std::unordered_map 上的线程安全包装器

使用线程不安全的静态变量锁定嵌套函数

线程安全的定义

std::map 和线程安全的奇怪问题

std::map 访问线程是不是安全,如果它的迭代器永远不会失效