Spinlock

Материал из Википедии — свободной энциклопедии
Перейти к: навигация, поиск

Спинлок (англ. Spinlock — циклическая блокировка) — низкоуровневый примитив синхронизации, применяемый в многопроцессорных системах для реализации взаимного исключения.

Физическая реализация[править | править исходный текст]

Физически спинлок представляет собой переменную в памяти и реализуется на атомарных операциях, которые должны присутствовать в системе команд процессора. Каждый процессор, желающий получить доступ к разделяемому ресурсу, атомарно записывает условное значение «занято» в эту переменную, используя аналог операции swap (в архитектуре x86 — xchg). Если предыдущее значение переменной (возвращаемое командой) было «свободно» то считается, что данный процессор получил доступ к ресурсу, в противном случае, процессор возвращается к операции swap и крутится в цикле ожидая, пока спинлок будет освобождён. После работы с разделяемым ресурсом процессор-владелец спинлока должен записать в него условное значение «свободно».

Пример реализации спинлока на ассемблере x86:

mov eax, spinlock_address
mov ebx, SPINLOCK_BUSY
 
wait_cycle:
lock xchg [eax], ebx
cmp ebx, SPINLOCK_FREE
jnz wait_cycle
 
;<спинлок захвачен данным процессором, работа с разделяемым ресурсом>
 
mov eax, spinlock_address
mov ebx, SPINLOCK_FREE
lock xchg [eax], ebx

Более интеллектуальная реализация будет использовать обычную, а не атомарную операцию, для опроса в цикле, а атомарную операцию — только для попыток захвата. Дело в том, что реализация атомарных операций с памятью происходит путем аппаратного блокирования системной шины процессором на время выполнения атомарной операции (которая включает чтение, модификацию и запись). Во время выполнения этих трёх операций выполнение каких-либо других операций на шине невозможно, что снижает производительность других процессоров в системе (если они разделяют общую шину), даже если они не имеют никакого отношения к данному спинлоку.

Также используются т. н. queued spinlocks — «спинлоки с очередью». В них вместо присвоения 0 или 1 в атомарную переменную используется атомарное добавление структуры на голову списка, при том, что голова списка есть атомарная переменная типа «указатель».

Полезные свойства queued spinlockов:

  • гарантия порядка предоставления в порядке запроса, гарантия от «голоданий»
  • в цикле опроса каждый процессор опрашивает свою локальную переменную
  • ровно 1 атомарная операция при захвате и ровно 1 при освобождении

Спинлоки применяются для синхронизации небольших участков кода, когда использование более сложных механизмов неоправданно или невозможно. Реализация примитивов синхронизации и диспетчера потоков обязательно требует блокировок, защищающих списки потоков, готовых к исполнению, и списки потоков, ожидающих на объектах. Такая блокировка может быть только спинлоком ввиду её очень низкого уровня. Таким образом, спинлок есть низший примитив синхронизации, на котором основана реализация всех остальных.

Специфика многопроцессорных и однопроцессорных конфигураций[править | править исходный текст]

Бытует распространённое мнение о том, что в пользовательских приложениях, работающих под управлением многозадачных ОС, использование спинлоков недопустимо, так как ожидание освобождения спинлока приводит к активному ожиданию в цикле, впустую расходующее вычислительные ресурсы процессора, а для синхронизации пользовательских программ должны применяться высокоуровневые примитивы, которые предполагают пассивное ожидание — если данная нить не может продолжить выполнение, то она отдаёт управление ОС, а не крутится в цикле ожидания спинлока (который потенциально может быть бесконечным). На самом деле данное утверждение является справедливым на 100 % только для однопроцессорных систем. Во многих случаях применение спинлоков в SMP-конфигурациях ведёт к повышению эффективности в случае, если опрос и захват спинлока оказываются быстрее, чем вызов захвата мьютекса в ядре.

Главным критерием здесь является contention — «жёсткость» соревнования за ресурс. Слабонагруженный ресурс, не являющийся популярным местом исполнения, ведёт себя иначе, чем сильно нагруженный, захватываемый и освобождаемый очень часто.

Кроме того, в той же Windows есть разновидности мьютексов (например, общеизвестная CRITICAL_SECTION, или же FAST_MUTEX в ядре), которые сначала работают как spinlock, используя опрос значения в памяти, и только потом, по истечении большого количества опросов, переходят в ядро к блокирующему ожиданию. Такие объекты сочетают лучшие качества спинлоков (минимальная цена захвата) и мьютексов (отсутствие растраты ресурса процессора на опрос).

Применение спинлоков[править | править исходный текст]

Случаи, когда применение в пространстве пользователя спинлоков даёт ощутимый эффект:

  • Внутри участка защищаемого кода находится несколько связанных переменных, время модификации которых может быть в сотни и даже тысячи раз меньше, чем переключение контекста процессором, что является особенно дорогой операцией, особенно на современных системах.
  • Блокировка не участков кода, а данных (с каждой структурой данных, которая должна атомарно изменяться как единое целое, связан спинлок, её защищающий)
  • Оптимизация кода, когда необходимо уменьшить нагрузку, возникающую за счёт слишком частого переключения контекста

Случаи, когда применение спинлоков не оправдано и является пустой тратой процессорных ресурсов:

  • Длительные блокирующие операции внутри защищаемого участка кода (дисковый и сетевой ввод-вывод может выполняться очень долго по процессорным меркам)
  • Однопроцессорные конфигурации — весь остаток кванта времени процессор проводит в холостом цикле.

Проблемы спинлоков и методы их решения[править | править исходный текст]

На современных процессорах цикл ожидания может выполняться очень быстро за счет особенностей конвейерной архитектуры, что помимо наматывания холостых циклов может приводить к более интенсивному нагреву, чем при обычной работе.

В Pentium 4 и последующих моделях процессоров Intel введена специальная ассемблерная команда для вставки внутрь цикла pause (опкод 0xf3 0x90, аналогичный команде rep nop для совместимости со старыми процессорами) предназначение которой проинструктировать процессор, что данный цикл является циклом ожидания, и позволяет процессору с поддержкой нескольких потоков на одном ядре перейти к следующему потоку.

Версии Windows от Windows 7 оптимизированы на работу в качестве «гостя» в виртуальной машине, и в них вместо pause в случаях, когда ОС исполняется как гость, используется специальный вызов «уведомить гипервизор о том, что мы в цикле ожидания».

Альтернативы спинлоков[править | править исходный текст]

  • Спинлоки служат для обеспечения монопольного доступа потока к защищаемой структуре данных. При этом не делается ни различия между самими потоками, ни между производимыми операциями. Однако, зачастую, в реальных приложениях потоки можно разделить на «Читающие» и «Пишущие». Для данного несимметричного случая более целесообразно применять RWLock. Структура может быть одновременно использоваться неограниченным количеством потоков в режиме «только чтение», вместе с тем давая защиту целостности данных при приходе «пишущего» потока.
  • Существуют также алгоритмы без блокировок, основанные на атомарном детектировании коллизий. Они оптимизированы под оптимистичный случай, при котором вся проверка на коллизию сводится к одной атомарной ассемблерной операции (Compare And Swap, на x86-архитектуре команда cmpxchg)

Прочие модификации спинлоков[править | править исходный текст]

Спинлок с автоматическим наращиванием до захвата полноценного мьютекса после истечения какого-то количества оборотов цикла применяется, например, в критических секциях Windows для оптимизации, заключающейся в отсутствии обращений к мьютексу при отсутствии соревнования за ресурс.

См. также[править | править исходный текст]