C - изменчивый квалификатор при удержании блокировки
Нужен ли квалификатор volatile для переменных, доступ к которым возможен только при удержании блокировки? В этом коде можно удалить квалификатор volatile из n
возможно изменить поведение, когда concurrent_foo
выполняется одновременно.
#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
происходят, пока блокировка удерживается, и поэтому должны быть "правильными". Однако я не уверен, действительно ли мне это гарантировано. Изменчивый квалификатор предотвращает оптимизацию производительности, которую я хотел бы, и не будет влиять на результат операции (ни в коем случае не будет n
быть четным).