/* Compile: gcc -pthread -m32 -ansi x.c */
#include <stdio.h>
#include <inttypes.h>
#include <pthread.h>
static volatile uint64_t v = 0;
void *func (void *x) {
__sync_add_and_fetch (&v, 1);
return x;
}
int main (void) {
pthread_t t;
pthread_create (&t, NULL, func, NULL);
pthread_join (t, NULL);
printf ("v = %"PRIu64"\n", v);
return 0;
}
我有一个
uint64_t
变量,希望对其进行原子递增操作,因为该变量是多线程程序中的计数器。
为了实现原子性,我使用GCC的原子内置函数。如果我编译amd64系统(-m64),则生成的汇编代码很容易理解。通过使用
lock addq
,处理器保证递增操作是原子的。 400660: f0 48 83 05 d7 09 20 lock addq $0x1,0x2009d7(%rip)
但是同样的C代码在ia32系统(-m32)上产生了非常复杂的汇编代码:
804855a: a1 28 a0 04 08 mov 0x804a028,%eax
804855f: 8b 15 2c a0 04 08 mov 0x804a02c,%edx
8048565: 89 c1 mov %eax,%ecx
8048567: 89 d3 mov %edx,%ebx
8048569: 83 c1 01 add $0x1,%ecx
804856c: 83 d3 00 adc $0x0,%ebx
804856f: 89 ce mov %ecx,%esi
8048571: 89 d9 mov %ebx,%ecx
8048573: 89 f3 mov %esi,%ebx
8048575: f0 0f c7 0d 28 a0 04 lock cmpxchg8b 0x804a028
804857c: 08
804857d: 75 e6 jne 8048565 <func+0x15>
这是我不理解的地方:
-
lock cmpxchg8b
保证只有在目标地址中的期望值仍然存在时,才会写入更改后的变量。比较和交换保证原子性。但是,是什么保证了在0x804855a和0x804855f处读取变量的原子性?
可能“脏读”并不重要,但请问是否可以简要概述一下没有问题的证明?
此外:为什么生成的代码跳回到0x8048565而不是0x804855a?我确定只有其他编写者也只增加变量时,这才是正确的。这是
__sync_add_and_fetch
函数的一个暗示要求吗?
mov 0x804a028,%eax
+mov 0x804a02c,%edx
是否是原子性的。这是两个单独的32位加载; 它们不会融合成一个64位加载,因此它们不是原子性的。我编辑了你的回答来纠正这一点,但它将那整个手册引用部分留在那里,作为基本上不相关的“趣事”:对于正确性,lock
不要求对齐(但对于性能要求对齐)。 - Peter Cordes