boost vs std原子序列一致性语义

时间:2015-04-10 04:05:40

标签: c++ c++11 boost atomic memory-model

我想编写一个C ++无锁对象,其中有许多记录器线程记录到一个大的全局(非原子)环形缓冲区,偶尔的读取器线程想要在缓冲区中读取尽可能多的数据可能。我最终得到了一个全局原子计数器,记录器获取要写入的位置,每个记录器在写入之前以原子方式递增计数器。读者尝试读取缓冲区和per-logger本地(原子)变量,以了解特定缓冲区条目是否忙于由某个记录器写入,以避免使用它们。

所以我必须在纯读者线程和许多编写器线程之间进行同步。我觉得问题可以在不使用锁的情况下解决,我可以依靠“发生后”关系来确定我的程序是否正确。

我尝试过轻松的原子操作,但它不起作用:原子变量存储是释放和负载被获取,并且保证一些获取(及其后续工作)总是“发生在”某些发布之后(和它以前的工作)。这意味着读者线程(完全没有存储)无法保证在读取缓冲区之后“发生”某些事情,这意味着我不知道某些记录器是否覆盖了部分缓冲区线程正在阅读它。

所以我转向顺序一致性。对我来说,“原子”意味着Boost.Atomic,顺序一致性的概念有一个“模式”documented

  

通过Boost.Atomic使用协调线程的第三种模式   seq_cst用于协调:如果......

     
      
  1. thread1执行操作A,
  2.   
  3. thread1随后使用seq_cst执行任何操作,
  4.   
  5. thread1随后执行操作B,
  6.   
  7. thread2执行操作C,
  8.   
  9. thread2随后使用seq_cst,
  10. 执行任何操作   
  11. thread2随后执行操作D,
  12.         

    然后“A发生在D之前”或“C发生在B之前”。

请注意,第二行和第五行表示“任何操作”,不说是否修改任何内容或操作内容。这提供了我想要的保证。

所有人都很高兴,直到我看到Herb Sutter的题为“原子<> Weapnos”的谈话。他暗示的是seq_cst只是一个acq_rel,具有一致的原子商店排序的额外保证。我转向cppreference.com,它们有类似的描述。

所以我的问题:

  1. C ++ 11和Boost Atomic是否实现了相同的内存模型?
  2. 如果(1)为“是”,是否意味着Boost所描述的“模式”在某种程度上暗示了C ++ 11内存模型?怎么样?或者它是否意味着cppreference中的Boost或C ++ 11的文档是错误的?
  3. 如果(1)是“否”,或者(2)是“是,但Boost文档不正确”,有没有办法在C ++ 11中达到我想要的效果,即保证(之后的工作)一些原子存储发生在(前面的工作)一些原子载荷之后?

1 个答案:

答案 0 :(得分:1)

我在这里没有回答,所以我再次在Boost用户邮件列表中询问。 我也没有看到任何答案(除了建议调查 提升锁定,所以我计划问Herb Sutter(期待没有回答 无论如何)。但在此之前,我用Google搜索了一下“C ++内存模型” 更深刻。看完Hans Boehm的一页后 (http://www.hboehm.info/c++mm/),我可以回答我自己的大部分内容 题。我用谷歌搜索了一下,这次是为了“C ++数据竞赛”而且 降临在Bartosz Milewski的一页 (http://bartoszmilewski.com/2014/10/25/dealing-with-benign-data-races-the-c-way/)。 然后我可以回答更多我自己的问题。不幸的是,我还是 鉴于这些知识,我不知道该怎么做我想做的事情。也许 我想做的事实上在标准C ++中实际上是无法实现的。

我的第一部分问题:“C ++ 11和Boost.Atomic实现了吗? 相同的记忆模型?“答案主要是”是“。我的第二部分 问题:“如果(1)是'是',它是否意味着”模式“ Boost所描述的是C ++ 11内存模型所隐含的某些东西吗? 回答是,是的。 “怎么样?”通过这里找到的证据回答 (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2392.html)。 从本质上讲,对于无数据竞争的程序,一点点添加 acq_rel足以保证seq_cst所需的行为。 所以这两份文件虽然可能令人困惑,但也是正确的。

现在真正的问题:虽然(1)和(2)都得到“是”答案,但是我的 原来的程序错了!我忽略了(实际上,我没有意识到) C ++的重要规则:具有数据竞争的程序具有未定义的行为 (而不是“未指定”或“实施定义”)。那 是的,只有我的程序,编译器才能保证程序的行为 绝对没有数据竞争。没有锁,我的程序包含一个 数据竞争:纯读者线程可以随时读取,即使是一次 当记录器线程忙于写入时。这是“未定义的行为”, 规则说计算机可以做任何事情(“火上浇油”) 规则)。要修复它,必须使用Bartosz页面中的想法 Milewski我之前提到过,即更改环形缓冲区以包含 只有原子内容,以便编译器知道它的排序 重要且不得对标记为的操作进行重新排序 需要顺序一致性。如果需要开销最小化, 可以使用放松的原子操作来写入它。

不幸的是,这也适用于读者线程。我再也不能了 只是“memcpy”整个内存缓冲区。相反,我也必须使用 轻松的原子操作来读取缓冲区,一个接一个的字。 这会导致性能下降,但我实际上别无选择。幸运的是 我,翻斗车的表现对我来说并不重要:它很少 无论如何都要运行。但如果我确实想要“memcpy”的表现,我 会得到“没有解决方案”的答案:C ++没有提供“I。”的语义 知道有数据竞争,你可以在这里回复任何东西,但不要 搞砸我的程序“。要么你确保没有数据竞争 并支付成本,以确保一切都很好,或者你有一个数据 种族和编译器可以让你入狱。