Ответы:
Спин-блокировка - это способ защитить общий ресурс от изменения двумя или более процессами одновременно. Первый процесс, который пытается изменить ресурс, «получает» блокировку и продолжает свой путь, делая то, что ему нужно с ресурсом. Любые другие процессы, которые впоследствии пытаются получить блокировку, останавливаются; говорят, что они «вращаются на месте» в ожидании блокировки, которая будет снята первым процессом, и, таким образом, получат название «спин-блокировка».
Ядро Linux использует спин-блокировки для многих вещей, например, при отправке данных на определенное периферийное устройство. Большинство аппаратных периферийных устройств не предназначены для одновременного обновления нескольких состояний. Если должны произойти две разные модификации, одна должна строго следовать другой, они не могут перекрываться. Спин-замок обеспечивает необходимую защиту, гарантируя, что изменения происходят по одному.
Спин-блокировки являются проблемой, потому что вращение блокирует ядро ЦП этого потока от выполнения любой другой работы. В то время как ядро Linux предоставляет многозадачные сервисы для программ пользовательского пространства, работающих под ним, это универсальное средство многозадачности не распространяется на код ядра.
Эта ситуация меняется, и была на протяжении большей части существования Linux. До Linux 2.0 ядро было почти чисто однозадачной программой: всякий раз, когда в ЦП выполнялся код ядра, использовалось только одно ядро ЦП, поскольку существовала единая спин-блокировка, защищающая все общие ресурсы, называемая Большой блокировкой ядра (BKL). ). Начиная с Linux 2.2, BKL постепенно разбивается на множество независимых блокировок, каждая из которых защищает более сфокусированный класс ресурсов. Сегодня, с ядром 2.6, BKL все еще существует, но он используется только действительно старым кодом, который не может быть легко перемещен в более детальную блокировку. Теперь для многоядерного блока вполне возможно, чтобы каждый процессор выполнял полезный код ядра.
Существует ограничение на утилиту разбиения BKL, потому что в ядре Linux отсутствует общая многозадачность. Если ядро ЦП блокируется вращением при спин-блокировке ядра, его нельзя повторно выполнить, чтобы сделать что-то еще до снятия блокировки. Он просто сидит и вращается, пока замок не будет снят.
Спин-блокировки могут эффективно превратить монстр 16-ядерный блок в одноядерный блок, если рабочая нагрузка такова, что каждое ядро всегда ожидает одну спин-блокировку. Это основной предел масштабируемости ядра Linux: удвоение ядер ЦП со 2 до 4, вероятно, почти удвоит скорость Linux-бокса, но удвоение его с 16 до 32, вероятно, не будет, при большинстве рабочих нагрузок.
Спин-блокировка - это когда процесс непрерывно запрашивает удаление блокировки. Это считается плохим, потому что процесс потребляет циклы (обычно) без необходимости. Это не специфичный для Linux, а общий шаблон программирования. И хотя это, как правило, считается плохой практикой, на самом деле это правильное решение; Есть случаи, когда стоимость использования планировщика выше (с точки зрения циклов ЦП), чем стоимость нескольких циклов, которые, как ожидается, продлится спин-блокировка.
Пример спин-блокировки:
#!/bin/sh
#wait for some program to clear a lock before doing stuff
while [ -f /var/run/example.lock ]; do
sleep 1
done
#do stuff
Часто есть способ избежать блокировки вращения. Для этого конкретного примера есть инструмент Linux под названием inotifywait (он обычно не устанавливается по умолчанию). Если бы он был написан на C, вы бы просто использовали API inotify, который предоставляет Linux.
В том же примере с использованием inotifywait показано, как выполнить то же самое без спин-блокировки:
#/bin/sh
inotifywait -e delete_self /var/run/example.lock
#do stuff
Когда поток пытается получить блокировку, в случае сбоя может произойти три вещи, он может попытаться заблокировать, он может попробовать и продолжить, он может затем попытаться перейти в спящий режим, сообщая ОС, что он активируется, когда происходит какое-то событие.
Теперь попытка и продолжение занимает намного меньше времени, чем попытка и блокировка. Скажем на минуту, что попытка продолжения займет единицу времени, а попытка блокировки - сотню.
Теперь давайте предположим, что в среднем поток занимает 4 единицы времени, удерживая блокировку. Тратить 100 единиц расточительно. Таким образом, вместо этого вы пишете цикл «попробуй и продолжай». На четвертой попытке вы обычно получаете замок. Это спиновый замок. Это называется потому, что поток продолжает вращаться на месте, пока не получит блокировку.
Дополнительной мерой безопасности является ограничение количества циклов. Таким образом, в примере вы выполняете цикл for, например, шесть раз, если он терпит неудачу, тогда вы «пытаетесь и блокируете».
Если вы знаете, что поток всегда будет удерживать блокировку, скажем, на 200 единиц, то вы тратите время компьютера на каждую попытку и продолжаете.
Таким образом, в конце, спин-блокировка может быть очень эффективной или расточительной. Расточительно, когда «типичное» время удержания блокировки превышает время, необходимое для «попытки и блокировки». Эффективно, когда типичное время удержания блокировки намного меньше, чем время «попробовать и заблокировать».
Ps: Книга для чтения по темам - это «Учебник по темам», если вы все еще можете ее найти.
Замок является способом для двух или более задач (процессы, потоки) для синхронизации. В частности, когда обеим задачам требуется прерывистый доступ к ресурсу, который может быть использован только одной задачей за раз, это позволяет организациям не использовать ресурс одновременно. Чтобы получить доступ к ресурсу, задача должна выполнить следующие шаги:
take the lock
use the resource
release the lock
Взять блокировку невозможно, если другая задача уже взяла ее. (Думайте о замке как о физическом объекте токена. Либо объект находится в ящике, либо кто-то держит его в руках. Только ресурс, имеющий объект, может получить доступ к ресурсу.) Таким образом, «взять замок» действительно означает «подождите, пока Ни у кого другого нет замка, тогда возьми его ».
С точки зрения высокого уровня, существуют два основных способа реализации блокировок: спин-блокировки и условия. В случае спин-блокировки взятие блокировки означает просто «вращение» (т.е. ничего не делать в цикле) до тех пор, пока никто другой не получит блокировку. При определенных условиях, если задача пытается взять блокировку, но заблокирована из-за того, что ее удерживает другая задача, новичок попадает в очередь ожидания; операция освобождения сигнализирует любой ожидающей задаче, что блокировка теперь доступна.
(Этих объяснений недостаточно, чтобы позволить вам реализовать блокировку, потому что я ничего не сказал об атомарности. Но атомарность здесь не важна.)
Спин-блокировки явно расточительны: задача ожидания продолжает проверять, снята ли блокировка. Так почему и когда это используется? Спинлоки часто очень дешевы, если их не удерживать. Это делает его привлекательным, когда вероятность удержания замка невелика. Кроме того, спин-блокировки жизнеспособны только в том случае, если ожидается, что получение блокировки не займет много времени. Таким образом, спин-блокировки обычно используются в ситуациях, когда они будут удерживаться в течение очень короткого времени, поэтому ожидается, что большинство попыток будут успешными с первой попытки, а те, которые нуждаются в ожидании, не ждут долго.
Существует хорошее объяснение спин-блокировок и других механизмов параллелизма ядра Linux в драйверах устройств Linux , глава 5.
synchronized
это будет реализовано с помощью спин-блокировки: synchronized
блок может работать очень долго. synchronized
является языковой конструкцией, облегчающей использование блокировок в определенных случаях, а не примитивом для создания больших примитивов синхронизации.
Спин-блокировка - это блокировка, которая работает путем отключения планировщика и, возможно, прерывает (вариант irqsave) на том конкретном ядре, на котором установлена блокировка. Он отличается от мьютекса тем, что отключает планирование, так что только ваш поток может работать, пока удерживается спин-блокировка. Мьютекс позволяет планировать другие потоки с более высоким приоритетом, пока он удерживается, но не позволяет им одновременно выполнять защищенный раздел. Поскольку спин-блокировки отключают многозадачность, вы не можете взять спин-блокировку, а затем вызвать другой код, который попытается получить мьютекс. Ваш код внутри секции спин-блокировки никогда не должен спать (код обычно спит, когда встречается с заблокированным мьютексом или пустым семафором).
Другое отличие от мьютекса в том, что потоки обычно стоят в очереди на мьютекс, поэтому у мьютекса внизу есть очередь. Принимая во внимание, что spinlock просто гарантирует, что никакой другой поток не будет работать, даже если это необходимо. Следовательно, вы никогда не должны удерживать спин-блокировку при вызове функций вне вашего файла, которые, как вы уверены, не будут спать.
Если вы хотите поделиться своей спин-блокировкой с прерыванием, вы должны использовать вариант irqsave. Это не только отключит планировщик, но и отключит прерывания. Это имеет смысл правильно? Спинлок работает, убедившись, что больше ничего не будет работать. Если вы не хотите, чтобы прерывание запускалось, отключите его и перейдите в критический раздел.
На многоядерном компьютере спин-блокировка будет вращаться в ожидании другого ядра, которое удерживает блокировку, чтобы освободить ее. Это вращение происходит только на многоядерных машинах, потому что на одноядерных машинах этого не может быть (вы либо удерживаете спин-блокировку и продолжаете, либо никогда не запускаетесь до снятия блокировки).
Спинлок не расточит, где это имеет смысл. Для очень маленьких критических секций было бы расточительно выделить очередь задач мьютекса по сравнению с простым приостановлением работы планировщика на несколько микросекунд, которые требуются для завершения важной работы. Если вам нужно спать или удерживать блокировку во время операции ввода-вывода (которая может спать), используйте мьютекс. Конечно, никогда не блокируйте спин-блокировку, а затем попытайтесь разблокировать ее внутри прерывания. Хотя это будет работать, это будет похоже на ардуинскую хрень из while (flagnotset); в таком случае используйте семафор.
Возьмите спин-блокировку, когда вам нужно простое взаимное исключение для блоков транзакций памяти. Захватите мьютекс, если вы хотите, чтобы несколько потоков прекратились непосредственно перед блокировкой мьютекса, а затем выбирается поток с наивысшим приоритетом для продолжения, когда мьютекс станет свободным, а также когда вы заблокируете и отпустите в том же потоке. Захватите семафор, когда вы намереваетесь опубликовать его в одном потоке или прерывании, а затем - в другом. Это три немного разных способа обеспечения взаимного исключения, и они используются для слегка разных целей.