我在这里遇到并发问题。我有std::map
,有一个
偶尔的作家和来自不同线程的多个频繁读者,这位作家偶尔会在地图上添加键(键是std::string
),我无法保证读者何时进行阅读和停止阅读。我不想为读者设置锁,因为阅读非常频繁并经常检查锁会损害性能。
如果读者总是通过键(而不是map
迭代器)访问地图,它是否总是线程安全的?如果没有,任何想法如何设计代码,以便读者总是访问有效的密钥(或map
迭代器)?
使用不同容器解决这个问题的其他方法也是受欢迎的。
答案 0 :(得分:5)
我不同意之前的回答。当他们谈论“同时访问现有元素”时(在讨论insert()
时),这假定您已经拥有指向现有元素的指针/引用/迭代器。这基本上是承认在插入后地图不会在内存中移动元素。它还承认在插入过程中迭代地图是不安全的。
因此,只要有插入,尝试在同一容器上(同时)执行at()
就是数据竞争。在插入期间,映射必须更改某种内部状态(可能是指向树节点的指针)。如果at()
在操作期间捕获容器,则指针可能不处于一致状态。
一旦insert()
和at()
(或operator[]
)发生在QTransform transform;
transform.rotate(45);
ui->graphicsView->setTransform(transform);
和ui->graphicsView->rotate(45);
,您就需要某种外部同步(例如读写器锁定)同时。
答案 1 :(得分:2)
注意:从根本上编辑了答案
作为反射,我会锁定。
乍一看,似乎不需要锁定你的情况:标准库解决了线程安全问题:
23.2.2。容器数据竞赛
1)为了避免数据竞争(17.6.5.9),实现 应考虑以下函数为const:begin,end, rbegin,rend,front,back,data,find,lower_bound,upper_bound, equal_range,at和,除了关联或无序关联 容器,操作员[]。
2)尽管如此(17.6.5.9), 实现需要避免数据争用时的内容 在相同序列中的不同元素中包含的对象, 除了矢量,同时进行修改。
还有其他一些SO答案将此解释为线程安全保证,正如我最初所做的那样。
尽管如此,我们知道在插入完成时,容器中的迭代范围是不安全的。并且在以某种方式迭代以找到元素之前需要访问元素。因此,虽然标准阐明了当您已经拥有其地址时对于不同元素的可访问性的安全性,但措辞会使潜在的容器并发问题失效。
我尝试过在MSVC上进行多次读取和单次写入的模拟场景,但它从未失败过。但这并不足以说明问题:允许实现避免比标准中的foressen更多的数据竞争(见17.5.6.9)(或者我可能只是很多次幸运)。
最后,我发现两个严肃的(后C ++ 11)引用明确指出用户锁是安全的:
GNU document on concurrency in the standard library:" 标准对库提出要求,以确保库本身不会引起任何数据争用(...)用户代码必须防止并发函数调用,当一个或多个访问修改状态时,访问任何特定库对象的状态。 "
GotW #95 Solution: Thread Safety and Synchronization, by Herb Sutter:" 代码是否正确同步(...)?不。代码有一个线程读取(通过const操作)来自some_obj,第二个线程写入同一个变量。如果这些线程可以同时执行,那就是竞赛和直接不定期行为的直接票据。"
基于这两个几乎权威的解释,我修改了我的第一个答案并回到我最初的反应:你必须锁定你的并发访问。
或者,您可以使用非标准库同时实现地图,例如来自并行模式库的Microsoft's concurrent_unordered_map或来自线程构建的Intel's concurrent_unordered_map阻止(TBB)或无锁库,如本SO answer
中所述