【问题标题】:Do I need to protect read access to an STL container in a multithreading environment?我是否需要在多线程环境中保护对 STL 容器的读取访问?
【发布时间】:2010-09-16 07:11:21
【问题描述】:

我有一个 std::list 容器和这些线程:

  • 一个无限添加元素的编写器线程。

  • 一个读取器/写入器线程,在可用时读取和删除元素。

  • 多个读取器线程访问容器的 SIZE(通过使用 size() 方法)

有一个普通的互斥锁可以保护前两个线程对列表的访问。我的问题是,大小读取器线程是否也需要获取此互斥锁?我应该使用读/写互斥锁吗?

我在使用 Visual C++ 6 的 Windows 环境中。

更新:看起来答案还不清楚。总结主要疑问:考虑到我不需要确切的值(即我可以假设一个+/- 1 个变化)?竞争条件如何使我的 size() 调用返回无效值(即与好的值完全无关的值)?

答案:通常,必须保护读取器线程以避免出现竞争条件。尽管如此,在我看来,更新中提到的一些问题还没有得到解答。

提前致谢!

谢谢大家的回答!

【问题讨论】:

  • 不看代码就无法回答。读者线程如何遍历列表。读者是否有可能对突然删除的节点有一个迭代器?如果是这样,尝试移动到下一个或上一个迭代器现在变得未定义。
  • 好吧,SIZE 读取器线程(正在讨论的线程)不会遍历列表,只需调用 size() 方法。其他线程(写入器和读取器/写入器)均受互斥锁保护。
  • 必看讲座在这里:channel9.msdn.com/posts/…
  • 鉴于写入器和读取器/写入器线程可以更改大小,读取器线程需要与它们同步。这对于防止读取器线程被调整容器大小的线程抢占是必要的。 “访问单个变量”不是原子的。

标签: c++ multithreading visual-c++ stl concurrency


【解决方案1】:

size() 是否安全(对于您提供的“安全”的定义)取决于实现。即使您在您的平台上覆盖,对于您的编译器版本在您的线程库版本和 C 运行时的优化级别,请不要这样编码。它会回到字节你,调试将是地狱。你正在为失败做好准备。

【讨论】:

    【解决方案2】:

    您应该考虑一些 SLT 实现可能会在调用时计算大小。
    为了克服这个问题,您可以定义一个新变量

    volatile unsigned int ContainerSize = 0;
    

    仅在已受保护的更新调用中更新变量,但您可以在没有保护的情况下读取/测试变量(考虑到您不需要确切的值)。

    【讨论】:

      【解决方案3】:

      VC++ 版本 6 中的 STL 不是线程安全的,请参阅 this recent question。所以看来你的问题有点无关紧要。即使做对了所有事情,你仍然可能遇到问题。

      【讨论】:

        【解决方案4】:

        我会说不。在这种情况下。

        如果没有互斥体,您可能会发现 size() 在添加或删除项目时偶尔会返回错误值。如果这对你来说是可以接受的,那就去做吧。

        但是,如果您在读者需要知道列表时需要准确的列表大小,则除了 add 和 erase 调用之外,您还必须在每个 size 调用周围放置一个关键部分。

        附言。 VC6 size() 只返回 _Size 内部成员,因此没有互斥锁不会对您的特定实现造成问题,除非在添加第二个元素时它可能返回 1,反之亦然。

        PPS。有人提到了 RW 锁,这是一件好事,尤其是当您以后想访问列表对象时。将您的互斥锁更改为 Boost::shared_mutex 将是有益的。但是,如果您调用的只是 size(),则不需要任何互斥锁。

        【讨论】:

        • 我不相信假设变量读/写是原子的是安全的,所以你真的应该保护调用。读写锁就是为这样的情况而设计的,所以加一个的开销应该不会太大。
        • 但在我的情况下,偶尔获得错误的值是可以接受的。我还需要保护电话吗?事实上,这些线程是我的应用程序的关键核心部分,因此避免额外的锁是首选。
        • 我想在我们明确回答这个问题之前先看看代码,但从描述中你不能假设这是安全的。
        • 其实你还是需要同步。当读者赶上作者时,可能会出现竞争条件……如果读者获得对未完全由作者初始化的对象的访问权限,这可能会导致未定义的行为。
        • 您需要阅读他的问题 - 在他的情况下,答案是他不需要锁定其他线程。在它们中调用 size() 不会破坏任何东西,最坏的情况是它们偶尔会一个接一个地返回。
        【解决方案5】:

        查看英特尔开源Threading Building Blocks 库提供的并发容器。在Code Samples page 的“容器片段”下查看一些示例。它们具有用于向量、哈希映射和队列的并发/线程安全容器。

        【讨论】:

        • 看起来不错,但我当前的项目没有使用那个库 :-( 无论如何,对于一个开源项目,这个库值得仔细看看。
        • 即使是非开源项目也值得一看。英特尔也为其出售商业许可。
        【解决方案6】:

        是的。我建议用一个强制串行访问的类来包装你的 STL 库。或者找一个已经调试过的类似类。

        【讨论】:

          【解决方案7】:

          我不相信 STL 容器是线程安全的,因为没有一种跨平台处理线程的好方法。对 size() 的调用很简单,但仍需要对其进行保护。

          这听起来像是一个使用读写锁的好地方。

          【讨论】:

          • STL 是 C++ 实现的一部分。这意味着它不一定是便携式的。例如。查看 Microsoft/Dinkumware STL 实现作为 VC++ 的一部分。它可以使用 VC++ 扩展。事实上,对于 TR1,预计该库确实使用了编译器扩展。
          【解决方案8】:

          是的,读取线程需要某种互斥控制,否则写入会改变它下面的东西。

          一个读写器互斥体就足够了。但严格来说,这是一个特定于实现的问题。即使在代码中只读的 const 对象中,实现也可能具有可变成员。

          【讨论】:

          • 但问题是,如果尺寸阅读器可以读取无效值......我的意思是,一个损坏的值。
          • 我认为读写器锁在实际使用中是安全的,但就通用标准而言,我认为通常可能需要完整的互斥锁。
          猜你喜欢
          • 1970-01-01
          • 2015-07-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多