11

posix 标准说,诸如互斥锁之类的东西将强制执行内存同步。但是,编译器可能会重新排序内存访问。说我们有

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

它可能会通过编译器重新排序更改为下面的代码,对吧?

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

那么互斥锁如何同步内存访问呢?更准确地说,编译器如何知道不应该在锁定/解锁之间发生重新排序?

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

已编辑:因此,如果函数调用是编译器无法通过的,我们可以将其视为编译器内存屏障,例如

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

2 回答 2

8

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

也就是说,这种知识通常以一种微不足道的方式获得:编译器不会在调用可能使用或修改它们的外部函数时重新排序对(不可证明的本地)数据的访问。它应该知道一些特别的东西lock并且unlock能够重新排序。

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

We can easily imagine a compiler supporting function attributes for acquire/release semantics, making lock and unlock less than a full compiler barrier.

于 2013-01-21T11:53:48.197 回答
3

编译器不会在不清楚是否安全的地方重新排序。在您的“假设”示例中,您不是在提议重新排序的内存访问,而是在询问如果编译器完全更改代码顺序会怎样——它不会。编译器可能会做的事情是改变实际内存读/写的顺序,而不是函数调用(无论是否考虑这些内存访问)。

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

a = *pAddressA;
b = *pAddressB;

让我们考虑一下值pAddressB在寄存器pAddressA中而不在寄存器中的情况。编译器首先读取地址 B 是公平的游戏,然后将值移动pAddressA到同一个寄存器中,以便可以接收新位置。如果这些访问之间碰巧有函数调用,编译器就不能这样做。

于 2013-01-21T11:51:37.483 回答