在 x86 机器上,像 inc、addl 这样的指令不是原子的,并且在 SMP 环境下,在没有锁前缀的情况下使用它们是不安全的。但在UP环境下是安全的,inc、addl等简单指令不会被打断。
我的问题是,给出像
这样的 C 级声明x = x + 1;
是否可以保证 C 编译器始终使用 UP 安全指令,例如
incl %eax
但不是那些线程不安全的指令(比如在多条指令中实现 C 语句,可能会被上下文切换中断),即使在 UP 环境中也是如此?
绝对没有保证
"x - x + 1"
会在任何平台(包括x86)上编译为中断安全指令。对于特定的编译器和特定的处理器架构来说,它很可能是安全的,但标准中根本没有强制要求,并且标准是您获得的唯一保证。
你不能根据你认为的编译结果来认为任何东西都是安全的。即使特定的编译器/体系结构声明它是,依赖它也是非常糟糕的,因为它降低了可移植性。其他编译器、架构甚至同一编译器和架构上的更高版本都可以很容易地破坏您的代码。
x = x + 1
可以编译为任意序列,例如:
load r0,[x] ; load memory into reg 0
incr r0 ; increment reg 0
stor [x],r0 ; store reg 0 back to memory
在没有内存增量指令的CPU上。或者它可能会很聪明并将其编译为:
lock ; disable task switching (interrupts)
load r0,[x] ; load memory into reg 0
incr r0 ; increment reg 0
stor [x],r0 ; store reg 0 back to memory
unlock ; enable task switching (interrupts)
其中
lock
禁用中断,unlock
启用中断。但是,即使如此,在具有多个共享内存的 CPU 的体系结构中,这可能不是线程安全的(lock
可能只禁用一个 CPU 的中断),正如您已经指出的。
语言本身(或它的库,如果它没有内置到语言中)将提供线程安全的构造,您应该使用这些构造,而不是依赖于您对将生成什么机器代码的理解(或可能误解)。
诸如 Java
synchronized
和 pthread_mutex_lock()
(在某些操作系统下可用于 C)之类的东西是您想要研究的。
没有。
您可以使用“易失性”,它可以防止编译器将 x 保存在临时寄存器中,并且对于大多数目标来说,这实际上会产生预期的效果。但并不能保证。
为了安全起见,您应该使用一些内联汇编,或者如果您需要保持可移植性,请使用互斥体封装增量。
如果您使用 GLib,它们有用于 int 和指针原子操作的宏。
http://library.gnome.org/devel/glib/stable/glib-Atomic-Operations.html
在 GCC 的最新版本中,有 __sync_xxx 内在函数可以完全满足您的需求。
而不是写:
x += 1;
写下:
__sync_fetch_and_add(&x, 1);
gcc 将确保将其编译成原子操作码。现在大多数重要的拱门都支持这一点。
http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Atomic-Builtins.html
它最初源于 Intel 对 ia64 上的 C 的建议,但现在也发现它可以在许多其他架构上使用 gcc。所以它甚至有点便携。
我相信您需要求助于 SMP 目标库,或者滚动您自己的内联汇编代码。
C 编译器可以在多条指令中实现类似
x = x + 1
的语句。我建议使用操作系统锁定特定例程,例如 Windows 上的 InterlockedIncrement Function。
无论如何,只担心 x86 编码是非常不可移植的。 这是看似很小的编码任务之一,但事实证明它本身就是一个项目。 找到一个可以为各种平台解决此类问题的现有库项目,然后使用它。 从 kaizer.se 的说法来看,GLib 似乎就是其中之一。