У вас правильная идея, но ваш асм не работает:
cmpxchg
не может работать с непосредственным операндом, только регистры.
lock
не является допустимым префиксомдля mov
.mov
для выровненного адреса является атомарным в x86, так что вам все равно не нужно lock
.
Прошло некоторое время с тех пор, как я использовал синтаксис AT & T, надеюсь, я все запомнил:
spin_lock:
xorl %ecx, %ecx
incl %ecx # newVal = 1
spin_lock_retry:
xorl %eax, %eax # expected = 0
lock; cmpxchgl %ecx, (lock_addr)
jnz spin_lock_retry
ret
spin_unlock:
movl $0, (lock_addr) # atomic release-store
ret
Обратите внимание, что в GCC есть атомарные встроенные функции, поэтому вам не нужно использовать встроенный ассемблер для этого:
void spin_lock(int *p)
{
while(!__sync_bool_compare_and_swap(p, 0, 1));
}
void spin_unlock(int volatile *p)
{
asm volatile ("":::"memory"); // acts as a memory barrier.
*p = 0;
}
Как говорит Бо ниже, закрытые инструкции требуют затрат: каждаявы должны получить эксклюзивный доступ к строке кэша и заблокировать ее, пока lock cmpxchg
запускает , как для обычного хранения этой строки кэша, но удерживается в течение выполнения lock cmpxchg
.Это может задержать разблокирующий поток, особенно если несколько потоков ожидают блокировки.Даже без большого количества процессоров все еще легко и стоит оптимизировать:
void spin_lock(int volatile *p)
{
while(!__sync_bool_compare_and_swap(p, 0, 1))
{
// spin read-only until a cmpxchg might succeed
while(*p) _mm_pause(); // or maybe do{}while(*p) to pause first
}
}
Инструкция pause
жизненно важна для производительности на процессорах HyperThreading, когда у вас есть код, который вращается вот так - он позволяетвторой поток выполняется, пока первый поток вращается.На процессорах, которые не поддерживают pause
, он обрабатывается как nop
.
pause
, также предотвращает ошибочные спекуляции с порядком памяти при выходе из спин-цикла, когда наконец настало время сделатьснова настоящая работа. Какова цель инструкции «PAUSE» в x86?
Обратите внимание, что спин-блокировки на самом деле используются редко: обычно используется что-то вроде критической секции или futex.Они включают в себя спин-блокировку для повышения производительности при низкой конкуренции, но затем возвращаются к механизму сна и оповещения с помощью ОС.Они также могут принимать меры для улучшения справедливости и многих других вещей, которые не делает цикл cmpxchg
/ pause
.
Также обратите внимание, что cmpxchg
не требуется для простого спин-блокировки:Вы можете использовать xchg
и затем проверить, было ли старое значение 0 или нет.Выполнение меньшего количества работы внутри инструкции lock
может держать линию кэша закрепленной в течение меньшего времени.См. Блокировка вокруг манипуляций с памятью через встроенную сборку для полной реализации asm с использованием xchg
и pause
(но все еще без возврата к спящему с ОС, просто вращается бесконечно.)