【问题标题】:std::map thread-safetystd::map 线程安全
【发布时间】:2013-02-10 14:35:24
【问题描述】:

对 std::map 中对象的引用是线程安全的吗?

std::map< std::string, Object >   _objects;

map 可以从许多线程中更改,并且此访问是同步的,但对值(Object &)的引用只能从 1 个实例和线程访问。如果另一个线程将项目添加到地图,使用 Object & 进行写操作是否安全?会重新分配吗?

【问题讨论】:

标签: c++ multithreading map stdmap


【解决方案1】:

C++11 标准保证const 方法对容器的访问对于不同线程是安全的(即,两者都使用const 方法)。

此外,[container.requirements.dataraces] 状态

需要实现以避免数据竞争 同一序列中不同元素中包含的对象, 除了vector&lt;bool&gt;

换句话说,除了vector&lt;bool&gt; 修改不同的内容不是数据竞争。

现在,如果一个线程使另一个线程使用的迭代器无效,显然这是一场数据竞争(并导致未定义的行为)。如果一个线程对容器进行非const 访问,而另一个线程对容器进行const 访问,这就是数据竞争(和未定义的行为)。 (注意:出于多线程的目的,许多函数被“视为const”,包括beginend 和其他非const 的函数(和方法),因为它们返回非@987654331 @迭代器。出于线程安全原因,[] 包含在这组伪const 中,除了mapunordered_set 等——23.2.2.1)。

但是,如果您对容器中的某个元素有引用,并且在另一个线程中执行了不会使该引用无效的操作,并且从未在另一个线程中写入该元素,那么您可以安全地从中读取参考。同样,如果其他线程甚至从未读取过该元素,那么写入该元素不应导致未定义的行为。

对于标准参考,17.6.5.9.5 似乎保证标准库中的函数不会跑掉并不必要地读/写元素。

所以简短的回答:你是安全的,只要其他线程不直接与 map 中的特定条目混淆。

【讨论】:

  • 参见 [container.requirements.dataraces]/3。这样可以确保可以同时修改不同的元素,并且正如您所说,该元素不会被其他线程中的地图更新所修改,如 [associative.reqmnts]/9 所述
  • 你确定这里没有苦读吗?对元素的并发访问是安全的,使用const 成员是安全的,一些非const 成员不会使引用或迭代器无效——但非const 成员保证不会读取 (从而导致元素写入的数据竞争)在容器上执行无害操作时?我认为容器没有理由这样做,但我在 23.2.2 中没有看到不允许这样做。
  • A std::map 应该只读取一个元素的键(因为它不知道如何处理元素的其余部分,所以没有理由查看它)并且键是@987654341 @,所以尽管我认为没有明确的措辞,std::map 的任何成员都没有理由在修改映射时读取元素的任何非 const 部分。
  • 17.6.5.9.2 -- "C++ 标准库函数不得直接或间接访问当前线程以外的线程可访问的对象 (1.10),除非通过函数的直接或间接访问对象争论,包括这个。”当std::pair 被访问时,.second 成员不是,所以标准库不能访问它或导致它被访问(类似地通过 17.6.5.9.3,不能修改它)。我很满意。
  • 17.6.5.9.5 那么呢? “其规范要求”可能涵盖这一点。
【解决方案2】:

地图中的元素是稳定的,除非元素从地图中删除,否则它们不会被移动或失效。如果只有一个线程正在写入给定对象,并且对映射本身的更改正确同步,那么我相信它将是安全的。我确信它在实践中是安全的,我认为它在理论上也是安全的。

该标准保证不同的元素可以被不同的线程修改,在 [container.requirements.dataraces]

尽管如此 (17.6.5.9),当同时修改同一序列中不同元素中包含的对象的内容(vector&lt;bool&gt; 除外)时,需要实现以避免数据争用。

这仅允许您修改元素,而不是在修改元素时向地图中插入新元素。对于某些容器,例如std::vector,修改向量本身也可能通过重新分配和移动元素来修改元素,但[associative.reqmts]/9 确保std::map 不会使现有元素无效。

由于std::map 的成员函数不需要访问其元素的第二个成员(即mapped_type),我认为[res.on.data.races]/5 表示没有其他线程会与写入冲突该成员在修改地图时。 (感谢 Yakk 完成了最后的拼图)

【讨论】:

  • 我保持对 thread1 中的值的引用(并且只是在这个线程中),但使用来自另一个线程的另一个键添加另一个值
  • 是否保证在map中插入新元素不会干扰并发读取线程(访问const)?是否保证红/黑树中没有重组?如果插入导致节点分裂,而并发线程通过同一路径执行遍历怎么办?
  • @dimstamat,不,完全不能保证。插入一个新元素是一个非常量操作,因此会与任何其他操作(包括 const 操作)发生冲突。阅读另一个答案,它非常清楚地说明了这一点。
  • 没错!这就是我所理解的,但我对答案的最后一部分感到困惑。感谢您的澄清!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-08-24
  • 1970-01-01
  • 1970-01-01
  • 2010-12-23
  • 1970-01-01
  • 1970-01-01
  • 2018-04-07
相关资源
最近更新 更多