Spinlock explained
Большинство разработчиков пишут сложные программные комплексы на языках 3-го поколения. При этом их знания начинаются с языка и заканчиваются библиотеками для этого языка. Что происходит "за кулисами" - страшная тайна, доступная лишь немногим. Еще пару лет назад многопоточность и виртуальная память у меня асоциировались с космосом, о котором не хочется думать потому, что страшно и совершенно непонятно. Сейчас же я имею хорошее представление о том, как выполняются мои программы - от трансляции исходного кода в ассемблерные инструкции до цифрового логического уровня. Хочу сказать, что ничего страшного или сверхестественного нет ни в ассемблере, ни в операционной системе, ни даже в механизме работы процессора. Ну, и продемонстрирую это на примере реализации простейшего примитива синхронизации потоков под названием spinlock.
Механизм работы spinlock
Блокировка может находится в двух состояниях - свободна и захвачена. Ее состояние должно быть отражено в ячейке памяти, доступной двум или более потокам. Соответственно, лучшего места, чем оперативная память, для нее найти невозможно. Поток, который пытается захватить блокировку, обязан ждать, пока она не освободится. Ожидание представляет собой цикл попыток захватить блокировку и прерывание цикла в случае успеха. И никакие два потока не должны захватить блокировку одновременно.
Последнее предложение предыдущего абзаца диктует требование к операции захвата блокировки - она должна быть атомарной. Поскольку, грубо говоря, атомарной операцией является одна ассемблерная инструкция, то и захват блокировки должен выполнятся одной такой инструкцией. При этом, одним из аргументов должен быть адрес памяти, где хранится состояние блокировки.
Реализация spinlock
Код функций (объяснение дальше):
__declspec(naked) void __fastcall SpinLock(int *cookie) {
__asm {
mov eax, 1
spinLoop:
lock xchg eax, [ecx]
test eax, eax
jnz spinLoop
ret
}
}
__declspec(naked) void __fastcall SpinUnlock(int *cookie) {
__asm {
mov eax, 0
lock xchg eax, [ecx]
ret
}
}