【问题标题】:Unlocked access to stl vector::size safeness解锁对 stl vector::size 安全性的访问
【发布时间】:2009-08-13 14:03:59
【问题描述】:

我在一个 stl 向量上有几个作者(线程)和一个阅读器。

正常的写入和读取受互斥体保护,但我想避免在我拥有的循环上发生争用,我想知道 vector::size 是否足够安全,我想这取决于实现,但由于通常矢量动态内存对于存储的项目,存储大小的内存在重新分配期间不应失效。

我不介意误报,在 size > 0 之后,我实际上会锁定并再次检查,因此如果在另一个线程写入时读取 size() 不会出现段错误,它对我来说应该足够安全。

【问题讨论】:

    标签: c++ multithreading stl


    【解决方案1】:

    我不知道并发读取和写入整数段错误的实现(尽管 C++03 标准不禁止这样做,我不知道 POSIX 是否这样做)。如果向量使用 pImpl,并且不将大小存储在向量对象本身中,则在尝试从已在另一个线程中释放的 pImpl 对象中读取大小时可能会遇到问题。例如,我机器上的 GCC 确实使用了 pImpl(并且不直接存储大小 - 它是根据 begin() 和 end() 之间的差异计算的,因此在修改期间存在明显的竞争条件)。

    即使它没有崩溃,它也很可能给出一个毫无意义或错误的答案。如果您不锁定,那么您读取的值可能是:

    • 以非原子方式读取,这意味着您将获得一个值的最高有效一半和另一个值的最低有效一半。在实践中,读取 size_t 在大多数实现中可能是原子的,因为 size_t 有充分的理由成为架构的自然字长。但是如果发生这种情况,当“之前”和“之后”都不是 0 时,这可能会将值读取为 0。例如考虑转换 0x00FF -> 0x0100。如果你得到“之后”的下半部分和“之前”的上半部分,那么你已经读到了 0。

    • 任意陈旧。如果没有锁定(或其他一些内存屏障),您可以从缓存中获取值。如果该缓存不与其他 CPU/内核共享,并且如果您的架构没有所谓的“一致缓存”,那么运行不同线程的不同 CPU 或内核可能会在六周前改变大小,你永远不会查看新值。此外,不同的地址可能有不同的陈旧量 - 没有内存屏障,如果另一个线程执行了 push_back,您可以想象“看到”向量末尾的新值,但看不到“看到”增加的大小。

    很多这些问题都隐藏在常见的架构中。例如,x86 具有一致的缓存,MSVC 保证在访问volatile 对象时会出现完整的内存屏障。 ARM 不保证一致的缓存,但实际上多核 ARM 并不常见,因此双重检查锁定通常也可以在那里工作。

    这些保证解决了一些困难并允许进行一些优化,这就是为什么它们首先被提出,但它们不是通用的。显然,如果不做出超出 C++ 标准的一些假设,您根本无法编写多线程代码,但是您依赖的特定于供应商的保证越多,您的代码的可移植性就越低。除了参考特定实现之外,无法回答您的问题。

    如果您正在编写可移植的代码,那么您真的应该将所有内存读取和写入都视为潜在的线程自己的私有内存缓存。内存屏障(包括锁)是一种从其他线程“发布”您的写入和/或“导入”写入的方法。与版本控制系统(或您最喜欢的任何本地副本的其他示例)的类比很明显,不同之处在于,即使您不要求发布/导入内容,也可能随时发布/导入。当然,没有合并或冲突检测,除非行业最终在我不注意的时候实现了事务内存;-)

    在我看来,多线程代码应该首先避免共享内存,然后在绝对必要时加锁,然后配置文件,然后担心争用和无锁算法。进入最后一个阶段后,您需要研究并遵循针对特定编译器和架构的经过充分测试的原则和模式。 C++0x 将通过标准化一些你可以依赖的东西来有所帮助,Herb Sutter 的一些“有效并发”系列详细介绍了如何利用这些保证。其中一篇文章实现了无锁多写入器单读取器队列,它可能适合也可能不适合您的目的。

    【讨论】:

    • 非常彻底的答案。与版本控制的类比不仅贴切,而且很有用。
    【解决方案2】:

    听起来您遇到了生产者/消费者问题,并且您正在轮询向量的大小作为要消费的标志。最好使用信号量来控制您的阅读器尝试工作,并使用互斥锁来控制对向量的访问。当向量为空时,让读取器阻塞信号量,直到写入器在向量中放入一些东西并增加信号量。然后读取器和写入器都使用互斥锁来修改向量本身。如果可以阻止,请不要轮询。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-06-05
      • 2011-04-23
      • 1970-01-01
      • 2022-08-18
      • 2021-05-16
      • 2019-11-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多