Когда следует использовать спин-блокировку вместо мьютекса?

294

Я думаю, что оба выполняют одну и ту же работу, как вы решаете, какой из них использовать для синхронизации?

компиляции вентилятора
источник
1
возможный дубликат спинлока против семафора!
Пол Р
11
Мьютекс и семафор - не одно и то же, поэтому я не думаю, что это дубликат. В ответе указанной статьи говорится это правильно. Для получения более подробной информации см. Barrgroup.com/Embedded-Systems/How-To/RTOS-Mutex-Semaphore
наноквак

Ответы:

729

Теория

Теоретически, когда поток пытается заблокировать мьютекс, но это не удается, поскольку мьютекс уже заблокирован, он переходит в спящий режим, немедленно позволяя запустить другой поток. Он будет продолжать спать до тех пор, пока его не разбудят, что произойдет, когда мьютекс будет разблокирован тем потоком, который ранее удерживал блокировку. Когда поток пытается заблокировать спин-блокировку, но это не удается, он будет постоянно повторять попытку блокировки, пока, наконец, это не удастся; таким образом, он не позволит другому потоку занять свое место (однако операционная система принудительно переключится на другой поток, разумеется, как только будет превышен квант времени выполнения для текущего потока).

Эта проблема

Проблема с мьютексами заключается в том, что перевод потоков в спящий режим и их повторное включение - довольно дорогие операции, для них потребуется довольно много инструкций ЦП и, следовательно, потребуется некоторое время. Если теперь мьютекс был заблокирован только на очень короткий промежуток времени, время, потраченное на перевод потока в спящий режим и его повторное пробуждение, может превысить время, в течение которого поток фактически спал, и даже может превысить время, в течение которого поток потратили впустую постоянно опрос на спинлок. С другой стороны, опрос спин-блокировки будет постоянно тратить процессорное время, и если блокировка удерживается в течение более длительного времени, это будет тратить гораздо больше процессорного времени, и было бы намного лучше, если бы поток спал вместо этого.

Решение

Использование спин-блокировок в одноядерной / однопроцессорной системе обычно не имеет смысла, поскольку пока опрос спин-блокировки блокирует единственное доступное ядро ​​ЦП, никакой другой поток не может работать, а другой поток не может работать, блокировка не будет быть разблокированным либо. Таким образом, спин-блокировка тратит впустую только процессорное время в этих системах без реальной выгоды. Если вместо этого поток был переведен в спящий режим, другой поток мог бы запуститься сразу, возможно, разблокировав блокировку и затем позволив первому потоку продолжить обработку, как только он снова проснулся.

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

Практика

Поскольку очень часто программисты не могут заранее знать, будут ли мьютексы или спин-блокировки лучше (например, из-за того, что число ядер ЦП целевой архитектуры неизвестно), и операционные системы не могут знать, оптимизирован ли определенный фрагмент кода для одноядерных или В многоядерных средах большинство систем строго не различают мьютексы и спин-блокировки. Фактически, большинство современных операционных систем имеют гибридные мьютексы и гибридные спин-блокировки. Что это на самом деле означает?

Гибридный мьютекс сначала ведет себя как спин-блокировка в многоядерной системе. Если поток не может заблокировать мьютекс, он не будет немедленно переведен в спящий режим, поскольку мьютекс может довольно быстро разблокироваться, поэтому вместо этого мьютекс сначала будет вести себя точно как спин-блокировка. Только если блокировка все еще не была получена через определенное количество времени (или повторных попыток или любого другого фактора измерения), поток действительно переводится в режим ожидания. Если тот же код выполняется в системе с одним ядром, мьютекс не будет блокироваться, хотя, как показано выше, это не будет выгодно.

Поначалу гибридная спин-блокировка ведет себя как обычная спин-блокировка, но во избежание траты слишком большого времени процессора она может иметь стратегию отсрочки. Обычно он не переводит поток в спящий режим (поскольку вы не хотите, чтобы это происходило при использовании спин-блокировки), но он может решить остановить поток (либо сразу, либо через определенное время) и разрешить запуск другого потока Таким образом, увеличивается вероятность того, что спин-блокировка разблокирована (чистый переключатель потока обычно дешевле, чем тот, который включает в себя перевод потока в спящий режим и его повторное пробуждение позже, хотя и ненадолго).

Резюме

Если вы сомневаетесь, используйте мьютексы, они, как правило, лучший выбор, и большинство современных систем позволяют им в течение очень короткого промежутка времени, если это кажется полезным. Использование спин-блокировок иногда может улучшить производительность, но только при определенных условиях, и тот факт, что вы сомневаетесь, скорее говорит мне о том, что вы не работаете над каким-либо проектом в настоящее время, где спин-блокировка может быть полезной. Вы можете рассмотреть возможность использования своего собственного «объекта блокировки», который может использовать внутреннюю спин-блокировку или мьютекс (например, это поведение можно настроить при создании такого объекта), изначально использовать мьютексы везде, и если вы думаете, что использование спин-блокировки где-то может действительно помогите, попробуйте и сравните результаты (например, с помощью профилировщика), но обязательно протестируйте оба случая,

Обновление: предупреждение для iOS

На самом деле это не специфично для iOS, но iOS - это платформа, на которой большинство разработчиков могут столкнуться с этой проблемой: если ваша система имеет планировщик потоков, это не гарантирует, что какой-либо поток, независимо от его низкого приоритета, в конечном итоге получит шанс на запуск, тогда спин-блокировки могут привести к постоянным тупикам. Планировщик iOS различает разные классы потоков, и потоки в более низком классе будут работать, только если ни один из потоков в более высоком классе не хочет работать также. Для этого не существует стратегии отсрочки, поэтому, если у вас постоянно есть потоки высокого класса, потоки низкого класса никогда не получат процессорное время и, следовательно, не смогут выполнять какую-либо работу.

Проблема выглядит следующим образом: ваш код получает спин-блокировку в потоке класса с низким уровнем prio, и пока он находится в середине этой блокировки, квант времени превышен, и поток прекращает работу. Единственный способ, которым эта спин-блокировка может быть освобождена снова, - это если поток с низким уровнем prio снова получает процессорное время, но это не гарантируется. У вас может быть несколько потоков с высоким уровнем prio, которые постоянно хотят работать, и планировщик задач всегда будет определять их приоритетность. Один из них может наткнуться на спин-блокировку и попытаться получить ее, что, конечно, невозможно, и система заставит ее работать. Проблема в том, что поток, который дал, сразу же доступен для запуска снова! Имея более высокий приоритет, чем поток, удерживающий блокировку, поток, удерживающий блокировку, не имеет шансов получить время выполнения процессора.

Почему эта проблема не возникает с мьютексами? Когда высокоприоритетный поток не может получить мьютекс, он не даст, он может немного вращаться, но в конечном итоге будет отправлен в спящий режим. Спящий поток недоступен для запуска до тех пор, пока его не разбудит событие, например, событие, такое как разблокированный мьютекс, которого он ожидал. Apple знает об этой проблеме и в результате устарела OSSpinLock. Новый замок называется os_unfair_lock. Эта блокировка позволяет избежать ситуации, упомянутой выше, поскольку она знает о различных классах приоритетов потоков. Если вы уверены, что использование spinlocks - хорошая идея в вашем проекте iOS, используйте эту. Держись подальше отOSSpinLock! И ни при каких обстоятельствах не реализуйте свои собственные спин-блокировки в iOS! Если сомневаетесь, используйте мьютекс! Эта проблема не затрагивает macOS, поскольку у нее есть другой планировщик потоков, который не позволяет ни одному потоку (даже потокам с низким приоритетом) «работать всухую» на времени ЦП, однако там может возникнуть та же самая ситуация, которая затем приведет к очень плохой работе. производительность, таким образом, OSSpinLockне рекомендуется для MacOS.

Mecki
источник
3
превосходное объяснение ... У меня есть сомнения относительно спин-блокировки. Могу ли я использовать спин-блокировку в ISR? если нет, то почему нет
Харис
4
@ Mecki Если я не ошибаюсь, я полагаю, что вы предположили в своем ответе, что срезы времени происходят только в однопроцессорных системах. Это не правильно! Вы можете использовать спин-блокировку в однопроцессорной системе, и она будет вращаться до тех пор, пока не истечет квант времени. Тогда другой поток с таким же приоритетом может вступить во владение (так же, как вы описали для многопроцессорных систем).
fumoboy007
7
@ fumoboy007 "и он будет вращаться до тех пор, пока не истечет его квант времени" // Это означает, что вы тратите время ЦП / заряд батареи абсолютно бесплатно без какой-либо единственной выгоды, которая является совершенно идиотской. И нет, я нигде не сказал, что срезы времени происходят только в одноядерных системах, я сказал, что в одноядерных системах есть ТОЛЬКО срезы времени, в то время как есть РЕАЛЬНЫЙ параллелизм для многоядерных систем (а также срезы времени, но это не имеет отношения к тому, что я написал в своих Ответить); Кроме того, вы полностью упустили вопрос о том, что такое гибридный спинлок и почему он хорошо работает в одно- и многоядерных системах.
Меки
11
@ fumoboy007 Поток A удерживает блокировку и прерывается. Поток B запускается и хочет блокировку, но не может ее получить, поэтому он вращается. В многоядерной системе поток A может продолжать работать на другом ядре, в то время как поток B продолжает вращаться, снять блокировку, и поток B может продолжить работу в пределах своего текущего временного кванта. В одноядерной системе есть только одно ядро, которое может запустить Поток A, чтобы снять блокировку, и это ядро ​​остается занятым вращением потока B. Таким образом, нет никакой возможности, чтобы спин-блокировка могла быть выпущена до того, как поток В превысил свой временной квант, и, таким образом, все вращение - просто трата времени.
Меки
2
Если вы хотите узнать больше о спин-блокировках и мьютексах, реализованных в ядре Linux, я настоятельно рекомендую прочитать главу 5 замечательных драйверов устройств Linux, третье издание (LDD3) (мьютексы: стр. 109; спин-блокировки: стр. 116).
patryk.beza
7

Продолжая с предложением Mecki, эта статья потокового мьютекс против PTHREAD спина - блокировки на блоге Александра Сэндлера, Алекс на шоу Linux , как spinlockи mutexesможет быть реализовано , чтобы проверить поведение с помощью #ifdef.

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

TheCottonSilk
источник
6

Также обратите внимание, что в определенных средах и условиях (таких как запуск в окнах на уровне диспетчеризации> = УРОВЕНЬ ОТПРАВЛЕНИЯ) вы не можете использовать мьютекс, а скорее спин-блокировку. На unix - тоже самое.

Вот эквивалентный вопрос на сайте Unix для конкурентов stackexchange: /unix/5107/why-are-spin-locks-good-choices-in-linux-kernel-design-instead-of-something- Больше

Информация о диспетчеризации в системах Windows: http://download.microsoft.com/download/e/b/a/eba1050f-a31d-436b-9281-92cdfeae4b45/IRQL_thread.doc

Дэн Джобс
источник
6

Ответ Меки вполне приличен. Однако на одном процессоре использование спин-блокировки может иметь смысл, когда задача ожидает блокировки, которая будет предоставлена ​​программой обработки прерываний. Прерывание передало бы управление ISR, что подготовило бы ресурс для использования ожидающей задачей. Это закончится снятием блокировки, прежде чем вернуть управление прерванной задаче. Вращающаяся задача найдет спинлок доступным и продолжит работу.

AlanC
источник
2
Я не уверен, что полностью согласен с этим ответом. Один процессор, если задача удерживает блокировку ресурса, ISR не может безопасно продолжить и не может дождаться, пока задача разблокирует ресурс (поскольку задача, содержащая ресурс, была прервана). В таких случаях задача должна просто отключить прерывания, чтобы обеспечить исключение между собой и ISR. Конечно, это должно быть сделано за очень короткие промежутки времени.
user1202136
3

Механизмы синхронизации Spinlock и Mutex сегодня очень распространены.

Давайте сначала подумаем о Спинлоке.

По сути, это активное ожидание, что означает, что нам нужно дождаться снятия указанной блокировки, прежде чем мы сможем перейти к следующему действию. Концептуально очень просто, при этом его реализации не по делу. Например: если блокировка не была снята, то поток был выгружен и перешел в состояние сна, должны ли мы справиться с этим? Как бороться с блокировками синхронизации, когда два потока одновременно запрашивают доступ?

Как правило, наиболее интуитивной идеей является синхронизация через переменную для защиты критической секции. Концепция Mutex похожа, но они все же разные. Фокус на: использование процессора. Spinlock потребляет процессорное время, чтобы дождаться выполнения действия, и поэтому мы можем суммировать разницу между ними:

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

В Windows использование Spinlock обновит поток до DISPATCH_LEVEL, что в некоторых случаях может быть запрещено, поэтому на этот раз нам пришлось использовать Mutex (APC_LEVEL).

Маркус Торнтон
источник
-6

Использование спин-блокировок в одноядерной / однопроцессорной системе обычно не имеет смысла, поскольку пока опрос спин-блокировки блокирует единственное доступное ядро ​​ЦП, никакой другой поток не может работать, а другой поток не может работать, блокировка не будет быть разблокированным либо. IOW, спин-блокировка тратит впустую только процессорное время в этих системах без реальной выгоды

Это не верно. При использовании спин-блокировок в однопроцессорных системах потери процессоров не теряются, потому что, как только процесс получает спин-блокировку, вытеснение отключается, поэтому больше никто не вращается! Просто использовать его не имеет никакого смысла! Следовательно, спин-блокировки в системах Uni заменяются на preempt_disable во время компиляции ядром!

Ниланш Миттал
источник
Цитируемое все еще полностью верно. Если скомпилированный результат исходного кода не содержит спин-блокировки, то цитирование не имеет значения. Если предположить, что вы сказали правду о том, что ядро ​​заменяет спин-блокировки во время компиляции, как обрабатываются спин-блокировки при предварительной компиляции на другом компьютере, который может быть или не быть однопроцессорным, если только мы строго не говорим о спин-блокировки только в самом ядре?
Hydranix
Msgstr "Как только процесс получает спин-блокировку, приоритетное прерывание отключается". Вытеснение не отключается, когда процесс вращается. Если бы это было так, один процесс мог бы снять всю машину, просто войдя в спин-блокировку и не выходя из нее. Обратите внимание, что если ваш поток работает в пространстве ядра (а не в пространстве пользователя), взятие спин-блокировки действительно отключает вытеснение, но я не думаю, что это обсуждается здесь.
Константин Вайц
На время компиляции по ядру ?
Шиен
@konstantin FYI спин-блокировка может быть взята только в пространстве ядра. И когда взята блокировка вращения, вытеснение отключено на локальном процессоре.
Ниланш Миттал
@hydranix Разве вы не получили? Очевидно, что вы не можете скомпилировать модуль для ядра, в котором включен CONFIG_SMP, и запустить тот же модуль в ядре, для которого отключен CONFIG_SMP.
Ниланш Миттал