如何理解在独立的C或C++实现中的原子操作?

8

C11和C++11通过执行线程来定义原子性。尽管在托管环境中,“线程”是明确的,但在独立语言实现中,“线程”这个词是一个模糊的术语。

  1. 如何在必须在程序内部实现所有线程的自由实现中正式理解C11和C++11中指定的原子性?例如:ISR是否是单独的执行线程?
  2. 为什么标准委员会将原子性的定义限制在执行线程而不是代码排序领域中呢?
  3. 除了gcc之外,是否有其他嵌入式编译器支持C11/C++11原子性?

请注意,C++11在[intro.multithread]/1中定义了thread - NathanOliver
如果你只有ISR,而不是多个处理器/核心,我会说标准中相关的部分是关于信号而不是线程的。 - EOF
我不清楚“有序原子”中的“有序”是什么意思。原子操作可以防止数据竞争,但不能保证顺序。 - Pete Becker
@Pete Becker 请阅读此文http://en.cppreference.com/w/cpp/atomic/memory_order。它可能会解释为什么C11和C++11原子被称为有序的。 - mrn
@mrn - 我曾深入参与C++原子设计和文档编写;标准中它们并不被称为“有序原子”,而只是“原子”。在某些情况下,谈论“弱有序原子”作为一个子集是有意义的,但如果你询问C++标准中原子的规范,那么“原子”就足够了,也更加清晰明了。 - Pete Becker
显示剩余5条评论
2个回答

5
我的一种机械化(有些含糊)的方法来回答这种问题是,原子操作保证了三件事情:读写不会被上下文切换中断(所以你只看到实际存储在变量中的值);缓存会被清空(所以你不会看到旧的值);编译器不能在原子操作中移动指令(所以在原子访问之前发生的逻辑操作确实发生在该访问之前)。需要注意的是,我试图避免任何关于“线程”的概念,尽管这有点费力。
如果你正在编写自己的线程机制,这些属性显然非常重要。它们与你使用的线程机制的细节是正交的。
对于信号处理程序,当你需要检查从信号处理程序执行的代码中的值并且信号处理程序需要修改整个程序关心的值时,它们为你提供了一个立足之地。
我不确定标准是否正式地涉及ISR(相当确定它没有),但从这种机械化的角度来看,ISR与不是由调用raise引起的信号没有区别。它只是一个异步函数调用,并且占用从被中断的线程获取的堆栈空间。它绝对不是一个线程;它是现有线程上的寄生体。因此,对于ISR,我会选择信号的保证而不是线程的保证。

我仍然想知道的一件事是,为什么标准没有基于您所写的三个保证来定义原子性,而不涉及“线程”的概念。如果这样做,那么对于裸机语言实现来说,原子性意味着什么就毫无疑问了。 - mrn
@mrn - 我认为正是因为我所写的内容是机械的和含糊的。标准试图通过抽象机器来定义C++的语义,目前它没有“tearing”或“cache”的概念。实际上,在抽象机器中关于内存的内容非常少,这是有意为之的,以避免过度包容系统。如果你仔细看,你会发现当你调用operator newoperator delete时,你得到的唯一承诺是第一个“分配存储”,第二个“回收...存储”。... - Pete Becker
因此,对于原子操作,主要用途是在线程之间传递数值,并且定义是基于当数值在线程之间共享时会发生什么。 - Pete Becker
@Peter Becker 好的,谢谢您的解释。如果我自己尝试编写标准,可能会看到问题。 - mrn

3
原子操作是用来解决竞态条件的,因此如果没有线程,它们几乎没有意义。唯一可能出现竞争的其他情况是在信号处理程序中,此时 C 标准提供了无锁概念。
从这个角度看,独立环境和托管环境没有区别。线程和原子操作都是可选功能,如果独立环境支持两者,则必须符合两者的规范。如果只支持原子操作,则可以为信号处理程序提供无锁原子类型,但其他原子类型则无用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接