繁体   English   中英

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

[英]boost vs std atomic sequential consistency semantics

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

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

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

所以我转向顺序一致性。 对我来说,“原子”是指Boost.Atomic,其中顺序一致性概念有一个“模式” 记载

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

  1. thread1执行操作A,
  2. thread1随后用seq_cst执行任何操作,
  3. thread1随后执行操作B,
  4. thread2执行操作C,
  5. thread2随后用seq_cst执行任何操作,
  6. thread2随后执行操作D,

然后要么“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中实现我想要的效果,即保证(后续的工作) )一些原子存储发生在(前面的工作)一些原子载荷之后?

我在这里没有看到答案,所以我再次在Boost用户邮件列表中询问。 我也没有看到任何答案(除了建议调查Boost lockfree),所以我计划问Herb Sutter(无论如何都没有回答)。 但在此之前,我用Google搜索了“C ++内存模型”。 在阅读Hans Boehm( http://www.hboehm.info/c++mm/ )的页面后,我可以回答我自己的大部分问题。 我用Google搜索了一下,这次是为了“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 ++没有提供“我知道有数据竞争的语义,你可以在这里向我返回任何内容,但不要搞砸我的程序”。 要么你确保没有数据竞争并支付成本来定义一切,或者你有一个数据竞争,并允许编译器让你入狱。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM