UB 操作:a.store(acquire),则机器码会在 S 后面添加一个 mfence。此时实际产生的语义为:所有后续 S/L 操作禁止被 re-order 到本 S 前面。猜测是由于 x86_64 CPU 允许 S-L re-order,那么要保证此 S 后面的 S/L 不被重排到本 S 前,那么只能加入 fence 指令。
UB 操作:a.load(release) 产生的机器码等同于 a.load(acquire)。(注意并不符合类似 S-release 的语义 "所有前面 S/L 操作禁止被 re-order 到本 L 后面,如果要符合的话必须在本 L 前面加 mfence)"
UB 操作:a.store(acquire/consume/acq_rel/seq_cst) 产生的机器码相同,都是在本 S 后面加入一个 mfence 指令。猜测是由于 x86_64 中,只需要在 S 后面加一个 mfence,该 S 就能同时满足"本 S 前所有的 S/L 禁止被重排到本 S 之后(即 release 语义)"与"本 S 后所有的 S/L 禁止被重排到本 S 之前(类似 L-acquire 语义)"的两个条件
memory_order_acq_rel:适用于RMW操作。
1)本线程所有 S/L 操作(无论前后)禁止被 re-order 到本 S 前或后。
为什的只提到S?(TODO: 这个RMW操作一定是保证原子性的吗?)
其实是只需要关心 S 即可。因为RMW操作的特性类似一个 LS 操作,这里 S 已经作为了同步点,本线程 L 前的 S/L 即使重排也只能重排在本 L 后且本 S 前,即使发生这种重排也是没有关系的。而本 S 后所有的 S/L 都不可被重排到本 S 前,这样就间接保证了不可被重排到本 L 前,从而也保证了 acquire 语义。
2)执行了相应 release 操作的其他线程,其 release 前的所有 S 一定发生在本 S 前。
这句话其实暗含了其他线程造成的该原子变量本身的 S 一定是发生在本 L 前的。因为如果其他线程对该原子变量的改动还没有被本线程观察到的话,可以认为其他线程并没有写它,也就不存在同步问题。既然观察到了变化,那么这个变化伴随的更改顺序关系才需要被讨论。
由于 S 比 L 慢,因此这个语义只要求其他线程被 release 的 S 一定是发生在本 S 之前就可以,没必要严格到必须发生在本 L 之前,那样效率会变低。不过这个要求已经比单纯的 acquire 严格了,即要求其他线程被 release 的 S 可以发生在本 L 后但必须发生在本 S 前,而单纯的 acquire 只暗含本 L 之后,就能看到相关线程所有被 release 的 S 了