Теория
Теоретически, когда поток пытается заблокировать мьютекс, но это не удается, поскольку мьютекс уже заблокирован, он переходит в спящий режим, немедленно позволяя запустить другой поток. Он будет продолжать спать до тех пор, пока его не разбудят, что произойдет после того, как мьютекс будет разблокирован тем потоком, который ранее удерживал блокировку. Когда поток пытается заблокировать спин-блокировку, но это не удается, он будет постоянно повторять попытку блокировки, пока, наконец, это не удастся; таким образом, он не позволит другому потоку занять свое место (однако операционная система принудительно переключится на другой поток, разумеется, как только будет превышен квант времени выполнения для текущего потока).
Проблема
Проблема с мьютексами заключается в том, что перевод потоков в спящий режим и их повторное включение - довольно дорогие операции, для них потребуется довольно много инструкций ЦП и, следовательно, также потребуется некоторое время. Если теперь мьютекс блокировался только на очень короткое время, время, потраченное на перевод потока в спящий режим и его повторное пробуждение, может превысить время, в течение которого поток фактически спал, и даже может превысить время, в течение которого поток потратили впустую постоянно опрос на спинлок. С другой стороны, опрос спин-блокировки будет постоянно тратить процессорное время, и если блокировка удерживается дольше, это будет тратить гораздо больше процессорного времени, и было бы намного лучше, если бы поток спал вместо этого.
Решение
Использование спин-блокировок в одноядерной / однопроцессорной системе, как правило, не имеет смысла, поскольку, пока опрос с использованием спин-блокировки блокирует единственное доступное ядро ЦП, никакой другой поток не может работать, а другой поток не может работать, блокировка не будет разблокирована либо. Таким образом, спин-блокировка тратит впустую только процессорное время в этих системах без реальной выгоды. Если вместо этого поток был переведен в спящий режим, другой поток мог бы запуститься сразу, возможно, разблокировав блокировку и затем позволив первому потоку продолжить обработку, как только он снова проснулся.
В многоядерных / многопроцессорных системах с множеством блокировок, которые удерживаются только в течение очень короткого промежутка времени, время, затрачиваемое на постоянный перевод потоков в спящий режим и их повторное включение, может заметно снизить производительность во время выполнения. Когда вместо этого используются спин-блокировки, потоки получают возможность воспользоваться преимуществами своего полного кванта времени выполнения (всегда блокируются только на очень короткий период времени, но затем сразу продолжают свою работу), что приводит к гораздо более высокой пропускной способности обработки.
Практика
Поскольку очень часто программисты не могут заранее знать, будут ли мьютексы или спин-блокировки лучше (например, из-за того, что число ядер ЦП целевой архитектуры неизвестно), операционные системы также не могут знать, оптимизирован ли определенный фрагмент кода для одного -ядерные или многоядерные среды, большинство систем строго не различают мьютексы и спин-блокировки. Фактически, большинство современных операционных систем имеют гибридные мьютексы и гибридные спин-блокировки. Что это на самом деле означает?
Гибридный мьютекс сначала ведет себя как спин-блокировка в многоядерной системе. Если поток не может заблокировать мьютекс, он не будет сразу же переведен в спящий режим, поскольку мьютекс может довольно быстро разблокироваться, поэтому вместо этого мьютекс сначала будет вести себя точно так же, как спин-блокировка. Только если блокировка все еще не была получена через определенное количество времени (или повторных попыток или любого другого фактора измерения), поток действительно переводится в спящий режим. Если тот же код выполняется в системе с одним ядром, мьютекс не будет блокироваться, хотя, как показано выше, это не будет выгодно.
Поначалу гибридная спин-блокировка ведет себя как обычная спин-блокировка, но, чтобы не тратить слишком много процессорного времени, она может иметь стратегию отсрочки.Обычно он не переводит поток в спящий режим (поскольку вы не хотите, чтобы это происходило при использовании спин-блокировки), но он может решить остановить поток (либо сразу, либо через определенное время) и разрешить запуск другого потокаТаким образом, повышается вероятность того, что спин-блокировка разблокирована (чистый переключатель потока обычно дешевле, чем тот, который включает в себя перевод потока в спящий режим и его последующее повторное включение, хотя и ненадолго).
Резюме
Если вы сомневаетесь, используйте мьютексы, они, как правило, являются лучшим выбором, и большинство современных систем позволяют им в течение очень короткого промежутка времени, если это кажется полезным.Использование спин-блокировок иногда может улучшить производительность, но только при определенных условиях, и тот факт, что вы сомневаетесь, скорее говорит мне о том, что вы не работаете над каким-либо проектом, в котором спин-блокировка может быть полезной.Вы можете рассмотреть возможность использования своего собственного «объекта блокировки», который может использовать внутреннюю спин-блокировку или мьютекс (например, это поведение может быть настроено при создании такого объекта), изначально использовать мьютексы везде, и если вы думаете, что использование спин-блокировки где-то может действительнопомогите, попробуйте и сравните результаты (например, с помощью профилировщика), но обязательно протестируйте оба варианта, одноядерную и многоядерную систему, прежде чем делать выводы (и, возможно, разные операционные системы, если ваш кодбудет кроссплатформенным).
Обновление: предупреждение для iOS
На самом деле не специфично для iOS, но iOS - это платформа, на которой большинство разработчиков могут столкнуться с этой проблемой: если ваша система имеет планировщик потоков,это не гарантирует, что какой-либо поток, каким бы низким ни был его приоритет, в конечном итоге получит шанс на запуск, тогда спин-блокировки могут привести к постоянным тупикам.Планировщик iOS различает разные классы потоков, и потоки в более низком классе будут работать только в том случае, если ни один из потоков в более высоком классе также не хочет работать.Для этого не существует стратегии отсрочки, поэтому, если у вас постоянно есть потоки высокого класса, потоки низкого класса никогда не получат процессорное время и, следовательно, никогда не смогут выполнять какую-либо работу.
Проблема выглядит следующим образом: Ваш код получает спин-блокировку в потоке класса с низким уровнем prio, и пока он находится в середине этой блокировки, квант времени превышен, и поток прекращает работу.Единственный способ, которым эта спин-блокировка может быть освобождена снова, - это если поток с низким уровнем prio снова получает процессорное время, но это не гарантируется.У вас может быть несколько потоков с высоким уровнем prio, которые постоянно хотят работать, и планировщик задач всегда будет определять их приоритетность.Один из них может натолкнуться на спин-блокировку и попытаться получить ее, что, конечно, невозможно, и система заставит ее работать.Проблема в том, что поток, который дал, сразу же доступен для запуска снова!Имея более высокий приоритет, чем поток, удерживающий блокировку, поток, удерживающий блокировку, не имеет шансов получить время выполнения процессора.Либо другой поток получит время выполнения, либо поток, который только что выдался.
Почему эта проблема не возникает с мьютексами? Когда высокоприоритетный поток не может получить мьютекс, он не даст, он может немного вращаться, но в конечном итоге будет отправлен в спящий режим. Спящая нить недоступна для запуска, пока она не будет разбужена событием, например, событие, такое как мьютекс, был разблокирован, которого он ждал. Apple знает об этой проблеме и в результате устарела OSSpinLock
. Новый замок называется os_unfair_lock
. Эта блокировка позволяет избежать ситуации, упомянутой выше, поскольку она осведомлена о различных классах приоритетов потоков. Если вы уверены, что использование spinlocks - хорошая идея в вашем проекте iOS, используйте эту. Держитесь подальше от OSSpinLock
! И ни при каких обстоятельствах не реализуйте свои собственные спин-блокировки в iOS! Если сомневаетесь, используйте мьютекс! MacOS не подвержен этой проблеме, поскольку имеет другой планировщик потоков, который не позволяет ни одному потоку (даже потокам с низким приоритетом) «работать всухую» на процессоре, но при этом может возникнуть такая же ситуация, которая затем приведет к очень плохому результату. производительность, таким образом, OSSpinLock
устарела и в macOS.