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