您正在考虑顺序一致性,即最强(也是默认)的内存顺序。如果采用这种内存顺序,所有对原子变量的访问构成全序,确实无法触发断言。
然而,在此程序中,使用了较弱的内存顺序(释放存储并获取加载)。根据定义,这意味着您cannot假设操作的总顺序。特别是,您不能假设更改以相同的顺序对其他线程可见。 (仅限总订单每个人变量保证任何原子内存顺序,包括memory_order_relaxed
.)
各商店至x
and y
发生在不同的线程上,它们之间没有同步。的负载x
and y
发生在不同的线程上,它们之间没有同步。这意味着完全允许线程 c 看到x && ! y
线程 d 看到y && ! x
。 (我只是在这里缩写了 acquire-loads,不要将此语法视为顺序一致的加载。)
底线:一旦您使用比顺序一致性更弱的内存顺序,您就可以告别所有原子的全局状态的概念,即所有线程之间一致的概念。这正是为什么很多人建议坚持使用顺序一致性,除非您需要性能(顺便说一句,请记住测量它是否更快!)并且确定您在做什么。另外,征求第二意见。
现在,你是否会因此而受伤,是一个不同的问题。该标准只是根据用于描述标准要求的抽象机允许断言失败的场景。但是,您的编译器和/或 CPU 可能会出于某种原因而无法利用这一限额。因此,对于给定的编译器和 CPU,实际上您可能永远不会看到断言被触发。请记住,编译器或 CPU 可能始终使用stricter内存顺序比您要求的顺序要好,因为这永远不会违反标准的最低要求。它可能只会损失一些性能——但这并不包含在标准中。
更新回应评论:该标准没有定义一个线程看到另一个线程对原子的更改所需的时间的硬性上限。建议实施者价值观应该变得可见最终.
有测序保证,但与您的示例相关的保证不会阻止断言的触发。基本的获取-释放保证是如果:
- 线程 e 对原子变量执行释放存储
x
- 线程 f 从同一原子变量执行获取加载
- Then iff 读取的值是 e 存储的值,e 中的存储与 f 中的加载同步。这意味着 e 中的任何(原子和非原子)存储是,在这个线程中,在给定存储之前排序
x
,对于 f 中的任何操作都是可见的,即在这个线程中,在给定负载之后排序。 [请注意,对于除这两个线程之外的线程,不提供任何保证!]
因此,不能保证 fwill读取 e 存储的值,而不是 e.g.的一些旧值x
. If it doesn't读取更新后的值,然后负载也会读取not与存储同步,并且对于上述任何相关操作都没有顺序保证。
我将原子比作与相对论一致的、具有较少内存顺序的原子,其中有没有同时性的全局概念 https://en.wikipedia.org/wiki/Ladder_paradox.
PS:也就是说,原子加载不能只读取任意旧值。例如,如果一个线程执行某个线程的周期性增量(例如,按释放顺序)atomic<unsigned>
变量,初始化为 0,并且另一个线程定期从该变量加载(例如,使用获取顺序),那么,除了最终包装之外,后一个线程看到的值必须单调递增。但这是根据给定的排序规则得出的:一旦后一个线程读取了 5,则在从 4 增加到 5 之前发生的任何事情都处于读取 5 之后的任何事情的相对过去。事实上,除了换行之外的减少是甚至不允许memory_order_relaxed
, but此内存顺序并不对访问其他变量的相对顺序(如果有)做出任何承诺。