我在stl向量上有几个编写器(线程)和一个阅读器。
正常的写入和读取都是互斥保护的,但是我想避免在我拥有的循环上争用,我想知道vector :: size是否足够安全,我想这取决于实现,但是因为通常是矢量动态内存对于存储的项目,存储大小的内存不应在重新分配期间失效。
我不介意误报,大小> 0我实际上会锁定并再次检查,所以如果读取size()而另一个线程写入不会发生段错误,那么它对我来说应该足够安全。
答案 0 :(得分:8)
我不知道并发读取和写入整数段错误的实现(虽然C ++ 03标准没有禁止这一点,我不知道POSIX是否这样做)。如果向量使用pImpl,并且没有将大小存储在向量对象本身中,那么在尝试从另一个线程中释放的pImpl对象读取大小时可能会出现问题。例如,我机器上的GCC确实使用了pImpl(并且没有直接存储大小 - 它被计算为begin()和end()之间的差异,因此在修改期间有明显的竞争条件机会。)
尽管它没有崩溃,但它可能会给出一个毫无意义或错误的答案。如果您没有锁定,那么您读取的值可以是:
非原子地阅读,意味着你得到一个值的最重要的一半和另一个值的最不重要的一半。实际上,在大多数实现中,读取size_t可能是原子的,因为size_t有很好的理由成为体系结构的自然字大小。但是如果它发生,当“之前”而不是“之后”都不为0时,这可以将值读为0.考虑例如转换0x00FF - > 0100。如果你得到“之后”的下半部分和“之前”的上半部分,你就读到了0。
任意陈旧。如果没有锁定(或其他一些内存屏障),您可以从缓存中获取值。如果该缓存不与其他CPU /核共享,并且您的体系结构没有所谓的“连贯缓存”,那么运行不同线程的不同CPU或核心可能在六周前改变了大小,您将永远不会看到新的价值。此外,不同的地址可能是不同的数量陈旧 - 没有内存障碍,如果另一个线程已经做了push_back,你可以想象“看到”你的向量末尾的新值但不“看到”增加的大小。
< / LI>很多这些问题都隐藏在常见的架构上。例如,x86具有连贯的缓存,MSVC在访问volatile
个对象时保证了完全的内存屏障。 ARM并不保证一致的缓存,但实际上多核ARM并不常见,因此双重检查锁定通常也适用于此。
这些保证解决了一些困难,并允许一些优化,这就是为什么它们首先制造,但它们不是普遍的。显然,如果不做出超出C ++标准的某些假设,就不能编写多线程代码,但是依赖于特定于供应商的保证,代码的可移植性就越低。除了参考特定的实现之外,不可能回答您的问题。
如果您正在编写可移植代码,那么您应该将所有内存读取和写入视为可能存在于线程自己的私有内存缓存中。内存障碍(包括锁)是一种“发布”写入和/或“导入”来自其他线程的写入的方法。与版本控制系统(或您最喜欢的其他任何本地副本示例)的类比很清楚,不同之处在于,即使您不要求它们也可以随时发布/导入。当然,没有合并或冲突检测,除非行业在我不看的时候最终实现了事务性内存; - )
在我看来,多线程代码应首先避免共享内存,然后在绝对必要时锁定,然后配置文件,然后担心争用和无锁算法。进入最后阶段后,您需要研究并遵循针对特定编译器和体系结构的经过良好测试的原则和模式。 C ++ 0x将通过标准化你可以依赖的一些东西来帮助,Herb Sutter的一些“有效并发”系列详细介绍了如何利用这些保证。其中一篇文章实现了无锁多写入器单读取器队列,可能适用于您的目的,也可能不适合您。
答案 1 :(得分:3)
听起来你有生产者/消费者问题而且你正在将矢量的大小轮询为消费的标志。最好使用信号量来控制读者尝试工作,并使用互斥量来控制对矢量的访问。当向量为空时,让读取器阻塞信号量,直到写入器将某些内容放入向量中并递增信号量。然后,读者和编写者都使用互斥锁来修改矢量本身。如果可以阻止,请不要轮询。