两个示例函数,循环对一个全局变量(c和d)进行累加计算,其中变量c有volatile关键字修饰,变量c没有。使用gcc O2优化编译后,使用objdump导出汇编。
从汇编代码看出,add函数被编译优化,内存变量d首先被放到了寄存器EAX,后续所有的累加操作,都是对EAX中值的累加,循环计算完成后,最后EAX结果写入内存变量d()。
addx函数没有被优化,循环中每一次累加前从内存变量c读取写入EAX,计算后再都被写入内存变量c(11c0),再从内存变量C读出放入EAX,做下一步循环计算。
两者比较之下,add对内存变量d的读写各只有1次,addx则在每次循环都有一次对变量c的读写操作,显然addx的性能会因此差很多。
但在多线程程序中,在循环累加的过程中,如果有另一个线程读取变量c和d,那么对于c变量可以获取到每一步循环的累加的中间值。而对于变量d,只会获取到初始值,直到add循环结束后才能获取到最终结果。
因此,在多线程并发程序中,共享变量最好加上volatile,特别是当这个变量在一个线程的逻辑过程中会多次变更,而另一个线程需要获取这个变量的实时状态时,否者可能只会获取到的该变量值可能已经过期。如果对于共享变量值的实时性并没有要求,则volatile并非必须。
volatile int c = 0;
int d = 0;
void add(int a, int b ) {
while ( d < 100 ) d = d + a + b;
}
void addx(int a, int b ) {
while ( c < 100 ) c = c + a + b;
}
0000000000001190 <add>:
1190: endbr64
1194: mov 0x2e7a(%rip),%eax # 4014 <d>
119a: cmp $0x63,%eax
119d: jg 11af <add+0x1f>
119f: nop
11a0: add %esi,%eax
11a2: add %edi,%eax
11a4: cmp $0x63,%eax
11a7: jle 11a0 <add+0x10>
11a9: mov %eax,0x2e65(%rip) # 4014 <d>
11af: retq
00000000000011b0 <addx>:
11b0: endbr64
11b4: jmp 11d0 <addx+0x20>
11b6: nopw %cs:0x0(%rax,%rax,1)
11bd: 00 00 00
11c0: mov 0x2e52(%rip),%eax # 4018 <c>
11c6: add %edi,%eax
11c8: add %esi,%eax
11ca: mov %eax,0x2e48(%rip) # 4018 <c>
11d0: mov 0x2e42(%rip),%eax # 4018 <c>
11d6: cmp $0x63,%eax
11d9: jle 11c0 <addx+0x10>
11db: retq
11dc: nopl 0x0(%rax)