i++ 原子操作的讨论
日期:2010-09-19 | 分类:c & c++
今天在CU上和cjaizss 讨论i++是不是原子操作的问题,收获很多,我之前有很多想法都是错误的,做个总结,记录下来。(习惯i++了,下面讨论的i和x是同一含义)
我对原子操作的理解是需要一条cpu指令的操作,如果i是32位的,并且没有跨页,那么i++是原子的,如果i是64位的,那么不是原子的,它需要两条cpu指令。
i 是32位时的汇编,addl $1, %eax是 i++ 对应的cpu指令
movl x, %eax
addl $1, %eax
movl %eax, x
i 是64位时的汇编,addl $1, %eax 和adcl $0, %edx 是i++对应的cpu指令
movl x, %eax
movl x+4, %edx
addl $1, %eax
adcl $0, %edx
movl %eax, x
movl %edx, x+4
但是i++是原子的(假设i是32位的),并不能保证多个线程i++,操作同一个i,可以得到正确的结果。因为还有寄存器的因素,多个cpu对应多个寄存器。每次要先把i从内存复制到寄存器,然后++,然后再把i复制到内存中,这需要至少3步。从这个意义上讲,说i++是原子的并不对。
gcc扩展提供了原子的i++,其对应的汇编是
lock
addl $1, x
多个cpu对应的寄存器不同,但是内存是相同的。(我记得cpu不能直接操作内存,这个addl $1, x,我不知道是啥意思)
我之前以为只要加上volatile关键字就可以保证,就不会出现i对应的内容在多个cpu的寄存器中不一致的情况,看来理解是错的,它只是在编译器优化时,提示每次都从内存中读取i的值。
观察优化后的代码(O3)也很有意思,
没有volatile修饰的i对应的汇编
addl $100000000, x
略去了循环,一次累加了所有的。有volatile时对应的汇编
movl x, %eax
addl $1, %edx
addl $1, %eax
cmpl $100000000, %edx
movl %eax, x
和没优化时差别不大。
测试代码
- #include <stdio.h>
- #include <signal.h>
- #include <pthread.h>
- #define MYNUM 100000000
- #ifdef A0
- int x = 0;
- # define ADD(x) do { (x)++; } while(0)
- #elif defined A1
- volatile sig_atomic_t x = 0;
- # define ADD(x) do { (x)++; } while(0)
- #elif defined A2
- int x = 0;
- # define ADD(x) do { __sync_fetch_and_add(&(x), 1); } while (0)
- #elif defined A3
- int x = 0;
- pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
- # define ADD(x) do { pthread_mutex_lock(&mutex); (x)++; pthread_mutex_unlock(&mutex); } while (0)
- #else
- # error "no add method"
- #endif
- void* func1(void* arg)
- {
- int i;
- for(i = 0; i < MYNUM; i++)
- ADD(x);
- }
- int main()
- {
- pthread_t id1,id2;
- pthread_create(&id1, NULL, func1, NULL);
- pthread_create(&id2, NULL, func1, NULL);
- pthread_join(id1, NULL);
- pthread_join(id2, NULL);
- printf("x=%d\nMYNUM*2=%d\n", x, MYNUM*2);
- return 0;
- }
编译
gcc -o a0 inc.c -pthread -DA0
gcc -o a1 inc.c -pthread -DA1
gcc -o a2 inc.c -pthread -DA2
gcc -o a3 inc.c -pthread -DA3
运行(只少要俩cpu)
A0和A1,无疑运行最快,当然答案是错的。
A2比A1慢8倍左右,可见cpu寄存器的影响之大。
A3比A2慢5倍左右,可见加锁始终是个问题,不得已而为之。
这些体会,可能不怎么正确,还有待进一步验证。
我不会汇编,这是个问题。08年就认识到这个问题了,现在感觉如此强烈,是时候学习了。
收藏到: Del.icio.us