一个偶然的作家,std :: map的多个频繁读者

All*_*nzi 5 c++ multithreading stdmap c++11

我在这里遇到并发问题.我有一个std::map,有一个偶尔的作家和来自不同线程的多个频繁读者,这个作者偶尔会std::string在地图上添加键(键是a ),我无法保证读者何时执行阅读和停止阅读.我不想为读者设置锁,因为阅读非常频繁并经常检查锁会损害性能.

如果读者总是通过键(而不是map迭代器)访问映射,它是否总是线程安全的?如果没有,任何想法如何设计代码,以便读者总是访问有效的键(或map迭代器)?

使用不同容器解决该问题的其他方法也是受欢迎的.

And*_*tur 5

我不同意以前的答案.当他们谈论"同时访问现有元素"时(谈论时insert()),这假设您已经拥有指向现有元素的指针/引用/迭代器.这基本上是承认在插入后地图不会在内存中移动元素.它还承认在插入过程中迭代地图是不安全的.

因此,只要你有一个插入,试图at()在同一个容器上(同时)进行数据竞争.在插入期间,映射必须更改某种内部状态(可能是指向树节点的指针).如果at()在该操作期间捕获容器,则指针可能不处于一致状态.

一旦有可能同时发生insert()at()(或operator[]),您就需要某种外部同步(例如读写器锁定).


Chr*_*phe 2

注意:根本上编辑过的答案

\n\n

作为条件反射,我会放一把锁。

\n\n

乍一看,似乎不需要在你的箱子里加锁:

\n\n
    \n
  • 对于insert(),据说“同时访问现有元素是安全的,但迭代容器中的范围则不安全”。
  • \n
  • 对于at(),据说: “同时访问或修改其他元素是安全的。”
  • \n
\n\n

标准库解决了线程安全方面的问题:

\n\n
\n

23.2.2. 容器数据竞争

\n\n

1) 为了避免数据竞争 (17.6.5.9),实现应将以下函数视为 const:begin、end、\n rbegin、rend、front、back、data、find、lower_bound、upper_bound、\n equal_range、at 和,除非在关联或无序关联\n 容器中,运算符[]。

\n\n

2) 尽管有 (17.6.5.9),\n当同一序列中不同\xef\xac\x80erent 元素中包含的对象的内容(向量除外)\n 被修改时,\n 需要实现以避免数据争用\xac\x81 同时进行。

\n
\n\n

还有其他几个 SO 答案将其解释为线程安全保证,就像我最初所做的那样。

\n\n

然而,我们知道当插入完成时,容器中的迭代范围并不安全。并且在以某种方式迭代找到该元素之前需要访问该元素。因此,虽然该标准阐明了当您已经拥有不同元素的地址时并发访问不同元素的安全性,但 该措辞留下了潜在的容器并发问题。

\n\n

我在MSVC上尝试过多次读取和一次写入的模拟场景,并且从未失败。但这还不足以说明这一点:允许实现避免比标准中预见的更多数据竞争(参见 17.5.6.9)(或者也许我只是很幸运)。

\n\n

最后,我发现了两个严肃的(C++11 后)参考资料,明确指出需要用户锁才能安全:

\n\n
    \n
  • 关于标准库中并发性的 GNU 文档:“标准对库提出了要求,以确保库本身不会引起数据竞争(...)用户代码必须防止访问任何特定库对象的并发函数调用\当其中一个或多个访问修改状态时, 的状态。

  • \n
  • GotW #95 解决方案:线程安全和同步,作者:Herb Sutter:“代码是否正确同步(...)?否。代码有一个线程从 some_obj 读取(通过 const 操作),第二个线程写入相同的内容变量。如果这些线程可以同时执行,那么 \xe2\x80\x99 就是一场竞赛,并且是通往未定义行为的直接不间断门票。

  • \n
\n\n

基于这两个几乎权威的解释,我修改了我的第一个答案并回到我最初的反应:你必须锁定你的并发访问。

\n\n

或者,您可以使用具有映射并发实现的非标准库,例如来自并行模式库的Microsoft 的并发_无序_映射或来自线程构建块 (TBB)的英特尔的并发_无序_映射 或无锁库,如此处所述所以答案

\n