对于仅在持有锁时访问的变量,我是否需要 volatile 限定符?n
在此代码中,可以从可能更改concurrent_foo
并发执行时的行为中删除 volatile 限定符。
#ifndef __GNUC__
#error __sync_lock builtins are only available with GCC
#endif
volatile int n = 0;
static volatile int lock = 0;
void concurrent_foo () {
while (__sync_lock_test_and_set (&lock, 1));
// Non-atomic operation, protected by spinlock above.
int x = n % 2 + 1;
n = n + x;
__sync_lock_release (&lock);
}
我知道 volatile 限定符指示编译器不要优化对变量的内存访问。我也知道 __sync_lock 内置函数发出一个(完整的?)内存屏障,内存访问不应该跨越。但是,在此示例代码中,可以安全地获取n
,将其缓存在寄存器中,计算新值,然后将其写回n
.
使用 GCC 编译到 i686 源代码,-O3
发现两次内存提取是不必要的:
concurrent_foo:
movl $1, %edx
.L2:
movl %edx, %eax
xchgl lock, %eax
testl %eax, %eax
jne .L2
movl n, %eax
movl n, %edx
movl %eax, %ecx
shrl $31, %ecx
addl %ecx, %eax
andl $1, %eax
subl %ecx, %eax
leal 1(%edx,%eax), %eax
movl %eax, n
movl $0, lock
ret
如果没有 volatile 限定符,我会得到微妙不同的代码,其中n
仅获取一次:
concurrent_foo:
movl $1, %edx
.L2:
movl %edx, %eax
xchgl lock, %eax
testl %eax, %eax
jne .L2
movl n, %edx
movl %edx, %ecx
shrl $31, %ecx
leal (%edx,%ecx), %eax
andl $1, %eax
subl %ecx, %eax
leal 1(%edx,%eax), %eax
movl %eax, n
movl $0, lock
ret
在这两种情况下,内存访问n
都会在持有锁时发生,因此应该是“正确的”。但是,我不确定我是否真的能保证这一点。volatile 限定符阻止了我想要的性能优化,并且不会影响操作的结果(在任何时候都不会n
是偶数)。