编译器内存屏障和互斥锁

时间:2013-01-21 11:44:08

标签: c++ multithreading compiler-construction

posix标准说像mutex这样的东西会强制执行内存同步。 但是,编译器可能会重新排序内存访问。 说我们有

lock(mutex);
setdata(0);
ready = 1;
unlock(mutex);

可能会通过编译器重新排序将其更改为以下代码,对吧?

ready = 1;
lock(mutex);
setdata(0);
unlock(mutex);

那么互斥量如何同步内存访问?更确切地说,编译器如何知道重锁不应该在锁定/解锁时发生?

实际上这里针对单线程方面,就绪分配重新排序是完全安全的,因为在函数调用锁(mutex)中没有使用ready。

EDITED: 因此,如果函数调用是编译器无法实现的, 我们可以将其视为编译器内存障碍,如

asm volatile("" ::: "memory")

2 个答案:

答案 0 :(得分:8)

一般的答案是,如果你想将它用于POSIX目标,你的编译器应该支持POSIX,这种支持意味着它应该知道避免重新排序锁定和解锁。

也就是说,这种知识通常是以一种微不足道的方式实现的:编译器不会通过调用可能使用或修改它们的外部函数来重新排序对(不可证实的本地)数据的访问。它应该知道关于lockunlock特殊以便能够重新排序。

不,它并不是那么简单,因为“对全局函数的调用始终是编译器障碍” - 我们添加“除非编译器知道有关该函数的特定内容”。它确实发生了:例如Linux上的pthread_self(NPTL)使用__const__属性声明,允许gcc重新排序pthread_self()次调用,甚至完全取消不必要的调用。

我们可以轻松想象支持获取/释放语义的函数属性的编译器,使lockunlock小于完整的编译器屏障

答案 1 :(得分:3)

编译器不会在不清楚安全的地方重新排序。在你的“假设”示例中,你没有提出重新排序的内存访问,你问的是编译器是否完全改变了代码排序 - 而且它不会。编译器可能做的事情是更改实际内存读/写的顺序,但不更改函数调用(使用或不使用这些内存访问)。

编译器可能重新排序内存访问的示例...假设您有以下代码:

a = *pAddressA;
b = *pAddressB;

并考虑pAddressB的值在寄存器中而pAddressA不在寄存器中的情况。编译器首先读取地址B,然后将pAddressA的值移到同一个寄存器中,以便可以接收新位置,这是公平的游戏。如果在这些访问之间碰巧发生了函数调用,则编译器无法执行此操作。