posix标准说像互斥体这样的东西会强制执行内存同步。
但是,编译器可能会重新排序内存访问。
说我们有
lock(mutex);
setdata(0);
ready = 1;
unlock(mutex);
可能会通过编译器重新排序将其更改为下面的代码,对吗?
ready = 1;
lock(mutex);
setdata(0);
unlock(mutex);
那么互斥量如何同步内存访问?更确切地说,编译器如何知道重锁不应该在锁定/解锁时发生?
实际上这里对于单线程方面,就绪分配重新排序是完全安全的,因为就绪不用于函数调用锁(互斥锁)。
编辑:
因此,如果函数调用是编译器无法实现的,
我们可以将其视为编译器内存屏障
asm volatile("" ::: "memory")
一般的答案是,如果你想将它用于POSIX目标,你的编译器应该支持POSIX,而这种支持意味着它应该知道避免重新排序锁定和解锁。
也就是说,这种知识通常以一种微不足道的方式实现:编译器不会通过调用可能使用或修改它们的外部函数来重新排序对(不可证实的本地)数据的访问。应该知道一些事情 特别 关于 lock
和 unlock
能够重新排序。
不,这并不是那么简单,因为“对全局函数的调用始终是编译器障碍” - 我们 应该 添加“除非编译器知道有关该函数的特定内容”。它真的发生了:例如 pthread_self
在Linux(NPTL)上声明 __const__
属性,允许 gcc
重新排序 pthread_self()
电话,甚至完全消除不必要的电话。
我们很容易 想像 一个支持获取/释放语义的函数属性的编译器 lock
和 unlock
不到一个 充分 编译屏障。
编译器不会在不清楚它是否安全的情况下重新排序。在你的“假设”示例中,你没有提出重新排序的内存访问,你问的是编译器是否完全改变了代码排序 - 而且它不会。编译器可能做的事情是改变实际内存读/写的顺序,但不改变函数调用(有或没有那些内存访问)。
编译器可能重新排序内存访问的示例...假设您有以下代码:
a = *pAddressA;
b = *pAddressB;
并让我们考虑一下其价值的情况 pAddressB
是在注册的同时 pAddressA
不是。编译器首先读取地址B,然后移动值的公平游戏 pAddressA
进入同一个寄存器,以便可以接收新的位置。如果在这些访问之间碰巧发生了函数调用,则编译器无法执行此操作。