Программирование
Современные блокировки в Swift: мьютекс и фреймворк Synchronization
Фреймворк Synchronization вводит мьютексы — современные блокировки Swift для создания исключительного доступа к данным. Он отлично работает с Swift Concurrency и предоставляет решение для не-sendable типов, без введения накладных расходов на акторы.
Swift предлагает несколько решений для блокировки доступа к изменяемому контенту и предотвращения так называемого состояния гонки. Блокировки, такие как NSLock, DispatchSemaphore или последовательная DispatchQueue, являются популярным выбором для многих. В некоторых статьях сравнивается их производительность и указывается, какая из них работает лучше всего, но я хотел бы представить вам современный вариант блокировки Swift, представленный в SE-433 Synchronous Mutual Exclusion Lock.
В этой статье я не буду рассказывать, какой блокировщик работает лучше всего, и не буду сравнивать их с этим новым вариантом. Каждый блокировщик может иметь свой профиль производительности и свои особенности. В этой статье мы рассмотрим стандартизированную версию так называемого мьютекс блокировщика.
Что такое блокировки в Swift
Блокировки в Swift, как и любые другие блокировки, позволяет обеспечить доступ к данным только одному потоку или задаче за раз. Это гарантирует безопасность доступа потоков и предотвращает исключения, вызванные одновременным доступом к данным. Последнее явление называется «гонка данных» и является частой причиной сбоев в приложениях. Подробнее об обнаружении гонок данных в коде можно прочитать в статье «Thread Sanitizer explained: Data Races in Swift».
В чем разница между мьютексом и блокировкой?
Все мьютексы являются блокировщиками, но не все блокировщики являются мьютексами. Мьютекс — это сокращение от mutual exclusion (взаимное исключение), и это специальный тип блокировки, который строго обеспечивает взаимное исключение, то есть только один поток может владеть им одновременно. Это владение означает, что его может разблокировать только тот же поток или задача, которые его заблокировали.
Эта строгая модель владения делает мьютексы надежными и помогает предотвратить определенные ошибки, связанные с неправильной разблокировкой. С другой стороны, термин «блокировка» более широк и может относиться к различным инструментам синхронизации, таким как повторно входимые блокировки, блокировки читателя-писателя или несправедливые блокировки. Хотя и те, и другие обеспечивают исключительный доступ к общим ресурсам, мьютексы ориентированы на простоту и строгое владение, в то время как блокировки предлагают большую гибкость и могут быть адаптированы к различным сценариям параллелизма.
Использование мьютекс блокировки Swift из фреймворка Synchronization
Теперь, когда мы знаем разницу между блокировкой и мьютексом, пришло время углубиться в фреймворк Synchronization от Apple. Этот фреймворк был анонсирован во время WWDC 24 и доступен в iOS 18 и macOS 15. Это важно для приложений, которые все еще должны поддерживать старые версии ОС.
В этом примере мы будем использовать мьютекс блокировку Swift для защиты счетчика. Это классический пример, демонстрирующий концепцию мьютекса:
final class Counter {
/// Use the Mutex to protect the count value.
private let count = Mutex<Int>(0)
/// Provide a public accessor to read the current count value.
var currentCount: Int {
count.withLock { currentCount in
return currentCount
}
}
func increment() {
count.withLock { currentCount in
currentCount += 1
}
}
func decrement() {
count.withLock { currentCount in
currentCount -= 1
}
}
}
Как видите, мы использовали мьютекс для защиты сохраненного значения счетчика. Это позволяет отслеживать count с помощью потокобезопасного метода. Мы создали общедоступные аксессоры для взаимодействия с count, а Mutex заставляет нас использовать метод withLock.
Этот метод обеспечивает inout доступ к свойству count. По сути, это означает, что мы можем напрямую взаимодействовать с изменяемым значением count. Таким образом, мы можем обновить счетчик с помощью:
currentCount += 1
Метод withLock форвардит все возвращаемые значения. Поэтому мы можем вернуть текущее значение счетчика, просто вернув параметр замыкания:
var currentCount: Int {
count.withLock { currentCount in
return currentCount
}
}
Вызов ошибок изнутри Mutex
Также можно вызвать ошибку изнутри замыкания withLock. Например, мы можем решить вызвать ошибку reachedZero, когда кто-то пытается уменьшить значение ниже нуля:
func decrement() throws {
try count.withLock { currentCount in
guard currentCount > 0 else {
throw Error.reachedZero
}
currentCount -= 1
}
}
Блокировка, которая отлично работает с Swift Concurrency
Мьютекс — это блокировка Swift, которая отлично работает с Swift Concurrency. Она безусловно является Sendable, что означает, что она обеспечивает потокобезопасный (Sendable) доступ к любому не-Sendable значению.
Недавно я работал с NSBezierPath, который является изменяемым не-Sendable типом. Я использовал этот путь для хранения касаний для записей симулятора в RocketSim. Я смог безопасно работать с этим экземпляром, заключив его в Mutex:
final class TouchesCapturer: Sendable {
let path = Mutex<NSBezierPath>(NSBezierPath())
func storeTouch(_ point: NSPoint) {
path.withLock { path in
path.move(to: point)
}
}
}
Это сделало мой TouchesCapturer совместимым с Sendable и продемонстрировало, как можно написать решение для работы с типами, несовместимыми с Sendable.
Мы можем продемонстрировать ту же концепцию, рассмотрев пример истории поиска, в котором мы храним поисковые запросы в изменяемом массиве:

Использование мьютекса как блокировки Swift может помочь вам создать sendable доступ к данным.
Не следует ли использовать актор вместо блокировок в Swift Concurrency?
Я уверен, что многие из вас задаются вопросом, почему Apple представила этот мьютекс, хотя она также работает над современными API параллелизма. Не следует ли использовать актор в таких случаях?
Акторы действительно являются фантастическим инструментом для защиты изменяемого состояния во многих сценариях, но они не всегда подходят. Есть ситуации, когда вам нужен синхронный, немедленный доступ к данным без использования ключевого слова async или точек приостановки. Иногда ваш код должен взаимодействовать с API или устаревшим кодом, который вообще не поддерживает Swift Concurrency, что делает акторы непрактичными или невозможными для использования.
Кроме того, акторы имеют определенные компромиссы в дизайне — они изолируют состояние и обеспечивают эксклюзивный доступ через асинхронные сообщения, что отлично подходит для безопасности, но может привести к дополнительным накладным расходам и сложности, когда требуется низкоуровневая прецизионная блокировка. В таких случаях Mutex предлагает легкий и знакомый примитив синхронизации, который можно использовать без изменения кода на асинхронный или рефакторинга всего вокруг await.
В конечном итоге, речь не идет о выборе одного из них в качестве универсального решения — речь идет о выборе правильного инструмента для конкретной задачи. Акторы проявляют себя, когда можно использовать асинхронную модель и воспользоваться преимуществами четкой логической изоляции, тогда как мьютексы заполняют пробелы там, где необходимы синхронный, немедленный доступ и минимальные сбои.
Заключение
Фреймворк Synchronization вводит мьютексы — современные блокировки Swift для создания исключительного доступа к данным. Он отлично работает с Swift Concurrency и предоставляет решение для не-sendable типов, без введения накладных расходов на акторы.
Спасибо!
-
Аналитика магазинов2 недели назад
Мобильный рынок Ближнего Востока: исследование Bidease и Sensor Tower выявляет драйверы роста
-
Интегрированные среды разработки3 недели назад
Chad: The Brainrot IDE — дикая среда разработки с играми и развлечениями
-
Новости4 недели назад
Видео и подкасты о мобильной разработке 2025.45
-
Новости3 недели назад
Видео и подкасты о мобильной разработке 2025.46

