在C ++中从/向std :: map读写的线程安全性

时间:2012-02-17 10:16:06

标签: c++ stl thread-safety

我有一个多线程应用程序,它使用共享数据结构,它包装了std :: map。

我知道STL不是线程安全的,同步所有线程是我的工作。另外我认为从列表中插入和删除元素不会使迭代器无效到其他元素。

所以我的代码中有每个元素的锁,这可以防止两个线程读/写同一个元素。让两个线程处理两个不同的对象。它们持有对象的锁,因此其他线程无法修改/删除它们的对象。

然而,第三个线程同时尝试删除第三个不同的对象。她已获得锁定,因此没有其他线程会尝试读取或删除它。

当其他线程正在读取/写入地图的其他元素时,从地图中删除元素是否可以线程安全?

3 个答案:

答案 0 :(得分:5)

请注意,知识 STL容器不是线程安全的错误!在C ++ 2011中,容器提供了合理的线程安全保证。它们可能与您希望的不同,但它们是合理且重要的:

  1. 如果没有线程修改容器的结构,可能会有并发线程读取同一容器对象的结构。
  2. 如果有一个容器结构的作者,则不应该对容器进行其他访问。
  3. 不同的对象是独立的,不同容器对象的并发访问不需要同步。
  4. 这些保证意味着如果多个线程只访问容器但不更改其结构,则不需要对容器进行任何同步。由于元素是由用户提供的,因此根据其线程安全保证,它们可能需要单独的同步。如果有线程修改容器的结构,则必须正确同步访问。

    对于您的情况,您必须确保没有线程读取地图,而某些线程正在修改它。由于迭代器和对象的引用不会失效,因此即使在修改地图时也可以通过迭代器或引用访问地图中的元素 - 当然,除非该元素可能被删除。

答案 1 :(得分:2)

没有。正如DiemtarKühl指出的那样,你需要锁上容器, 必须在您访问容器时获取。所以你的 大多数访问的场景是(C是容器锁, O单个对象的锁定:

acquire C
find object
acquire O
release C
process object
release O

如果删除,请:

acquire C
find object
acquire O
delete object
release O
release C

,或者如果您在决定删除之前需要先处理:

acquire C
find object
acquire O
release C
process, determine that deletion is needed
acquire C
release O
release C

这会产生一些问题。最明显的是RAII不可能 用于管理锁,至少不是以自然的方式。 (它可以 仍然用于确保在发生锁定时释放锁定 异常,但在第一个场景中释放容器锁 必须是手动的。)更重要的是,它会陷入僵局 至少两种情况:

  • 如果您的线程需要一次访问多个对象。在 在这种情况下,在第一个场景中,你有获得C的线程1, 然后获取O1,然后释放C.继续线程2获取 C,然后在O1上阻塞。线程1然后恢复,并决定它也 需要访问对象2.所以它试图获取C,并阻塞, 等待线程2释放它。 (线程2当然是被阻止的 直到线程1释放O1。)

  • 如果你正在使用第二种方案进行删除,那就足够了 第二个线程尝试访问您正在处理的对象 当你正在处理它。如上所述,第二个线程将阻塞 在O(第一个线程持有),第一个线程将阻止 在C上(第二个在场景中获得C)。两个线程都不会 去任何地方,都在等待另一个继续。

如果没有线程每个锁定多个对象,则第一个场景是 用于删除,模式将起作用。但它非常 脆弱 - 想象一个维护程序员太容易了 违反其中一个条件 - 我强烈建议 反对。 (当然,如果没有其他选择提供足够的 吞吐量,您可能必须使用它。甚至是第二种情况 删除可以使如果你在尝试之前释放O. 第二次收购C,然后重新获得O,一旦你有C.关键 条件是你必须总是获得C,然后按顺序获得O,和 当你有一个O时,你永远不会尝试获得C.)

另请注意,让每个对象包含互斥锁都很棘手,因为 您必须保持互斥锁,直到删除对象为止 地图。这意味着地图本身可以保存指向 对象(并在从对象中删除对象后保留指向该对象的指针) 映射,并释放锁并通过此指针删除对象),或 该对象保持指向互斥锁的指针。

最简单的解决方案是在C上使用单个锁,并对其进行维护 在处理O期间。如果处理时间不长,这个 可能是可以接受的如果您使用多线程的原因是 能够同时处理多个核心,这是行不通的。

如果不这样做,你可能要考虑在上面使用rwlock 容器,并在你持有O的整个过程中坚持它。简单 然后可以继续访问,因为它只是一个读访问权限和锁 允许多次读取访问。对于删除,您需要写访问权限, 这将阻止所有读取访问完成;你也会 仍然需要对第二种删除方案进行特殊处理, 因为尝试升级从读取到写入的访问可能会导致 死锁,完全如上所述。 (要从读取升级到写入, 没有其他线程可以保持读访问权。)

答案 2 :(得分:0)

  

当其他线程正在读/写地图的其他元素时,从地图中删除元素是否可以线程安全?

应该是,只要您正在阅读/写入的元素不是要删除的元素。