在 x86 机器上,像 inc、addl 这样的指令不是原子的,并且在 SMP 环境下,在没有锁定前缀的情况下使用它们是不安全的。但是在UP环境下是安全的,因为inc、addl等简单的指令不会被打断。
我的问题是,给定一个 C 级语句,例如
x = x + 1;
是否有任何保证 C 编译器将始终使用 UP 安全指令,例如
incl %eax
但不是那些线程不安全的指令(比如在可能被上下文切换中断的几条指令中实现 C 语句)即使在 UP 环境中?
在 x86 机器上,像 inc、addl 这样的指令不是原子的,并且在 SMP 环境下,在没有锁定前缀的情况下使用它们是不安全的。但是在UP环境下是安全的,因为inc、addl等简单的指令不会被打断。
我的问题是,给定一个 C 级语句,例如
x = x + 1;
是否有任何保证 C 编译器将始终使用 UP 安全指令,例如
incl %eax
但不是那些线程不安全的指令(比如在可能被上下文切换中断的几条指令中实现 C 语句)即使在 UP 环境中?
不。
您可以使用“volatile”,它可以防止编译器将 x 保存在临时寄存器中,并且对于大多数目标,这实际上会产生预期的效果。但不能保证。
为了安全起见,您应该使用一些内联汇编,或者如果您需要保持可移植性,请使用互斥锁封装增量。
如果您使用 GLib,它们具有用于 int 和指针原子操作的宏。
http://library.gnome.org/devel/glib/stable/glib-Atomic-Operations.html
绝对不能保证"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 的中断),正如您已经说过的那样。
语言本身(或它的库,如果它没有内置在语言中)将提供线程安全的构造,您应该使用这些构造,而不是依赖于您对将生成的机器代码的理解(或可能是误解)。
像 Javasynchronized
和pthread_mutex_lock()
(在某些操作系统下可用于 C)之类的东西是您想要研究的。
在最新版本的 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
它最初源于英特尔对 ia64 上的 C 的建议,但现在发现它也可以在许多其他拱门上使用 gcc。所以它甚至有点便携。
无论如何,只担心 x86 是非常不可移植的编码。这是看似很小的编码任务之一,但它本身就是一个项目。找到一个现有的库项目,为各种平台解决此类问题,并使用它。根据 kaizer.se 的说法,GLib 似乎就是其中之一。
x = x + 1
AC 编译器可以像在几个指令中一样实现一个语句。
您可以使用register关键字来提示编译器使用寄存器而不是内存,但编译器可以随意忽略它。
我建议使用操作系统锁定特定例程,例如 Windows 上的InterlockedIncrement 函数。
我相信您需要求助于 SMP 目标库或滚动您自己的内联汇编代码。