两个示例函数,循环对一个全局变量(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)