Какие знаешь синхронизации доступа к чтению и записи переменной?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Механизмы синхронизации доступа к чтению и записи переменных в iOS
В контексте многопоточного программирования под синхронизацией доступа подразумевается обеспечение корректного и безопасного обращения к общим данным из нескольких потоков одновременно. Основная проблема — гонка данных (race condition), когда результат выполнения зависит от порядка выполнения потоков. Для её решения в iOS/macOS применяется ряд механизмов.
1. Блокировки (Locks)
Мьютексы (Mutex)
Мьютекс (MUTual EXclusion) обеспечивает эксклюзивный доступ к ресурсу только одному потоку.
import Foundation
class MutexExample {
private var mutex = pthread_mutex_t()
private var sharedValue: Int = 0
init() {
pthread_mutex_init(&mutex, nil)
}
deinit {
pthread_mutex_destroy(&mutex)
}
func writeValue(_ newValue: Int) {
pthread_mutex_lock(&mutex)
sharedValue = newValue
pthread_mutex_unlock(&mutex)
}
func readValue() -> Int {
pthread_mutex_lock(&mutex)
let value = sharedValue
pthread_mutex_unlock(&mutex)
return value
}
}
NSLock и pthread_mutex_t — классические реализации. Важно всегда снимать блокировку, иначе deadlock.
Рекурсивный мьютекс (Recursive Mutex)
Позволяет одному потоку захватывать одну блокировку несколько раз.
let recursiveLock = NSRecursiveLock()
Используется когда функции могут рекурсивно вызывать себя из одного потока.
Read-Write Lock
Оптимизирован для частых операций чтения: допускает множественное чтение, но эксклюзивную запись.
import Foundation
class ReadWriteLock {
private var lock = pthread_rwlock_t()
private var resource: String = ""
init() {
pthread_rwlock_init(&lock, nil)
}
deinit {
pthread_rwlock_destroy(&lock)
}
var value: String {
get {
pthread_rwlock_rdlock(&lock)
defer { pthread_rwlock_unlock(&lock) }
return resource
}
set {
pthread_rwlock_wrlock(&lock)
defer { pthread_rwlock_unlock(&lock) }
resource = newValue
}
}
}
2. Семафоры (Semaphores)
DispatchSemaphore позволяет ограничить количество потоков, одновременно работающих с ресурсом.
let semaphore = DispatchSemaphore(value: 1) // Двоичный семафор как мьютекс
func accessSharedResource() {
semaphore.wait() // Уменьшает счётчик
// Критическая секция
semaphore.signal() // Увеличивает счётчик
}
3. Серийные очереди (Serial Queues)
DispatchQueue с атрибутом .serial обеспечивает последовательное выполнение задач.
let serialQueue = DispatchQueue(label: "com.example.serial")
var sharedData = [Int]()
func addValue(_ value: Int) {
serialQueue.sync {
sharedData.append(value)
}
}
func readData() -> [Int] {
return serialQueue.sync {
return sharedData
}
}
sync блокирует вызывающий поток, async — не блокирует.
4. Concurrent очереди с барьерами
Для concurrent очередей барьер (barrier) обеспечивает эксклюзивность записи при параллельном чтении.
let concurrentQueue = DispatchQueue(label: "com.example.concurrent",
attributes: .concurrent)
var dictionary = [String: Any]()
func setValue(_ value: Any, forKey key: String) {
concurrentQueue.async(flags: .barrier) {
dictionary[key] = value
}
}
func getValue(forKey key: String) -> Any? {
return concurrentQueue.sync {
return dictionary[key]
}
}
Барьерная задача выполняется только когда все предыдущие завершены.
5. Атомарные операции
Низкоуровневые атомарные операции через OSAtomic или Atomic свойства.
import Darwin
class AtomicCounter {
private var counter: Int32 = 0
func increment() {
OSAtomicIncrement32(&counter)
}
var value: Int32 {
return counter
}
}
Важно: Многие OSAtomic функции deprecated с iOS 10. Альтернатива — std::atomic в C++ или Atomic в Swift через @atomic (доступен с Swift 5.5+ для ограниченных типов).
6. Actors (Swift 5.5+)
Actor — современная абстракция языка Swift для изоляции состояния.
actor BankAccount {
private var balance: Double = 0.0
func deposit(_ amount: Double) {
balance += amount
}
func withdraw(_ amount: Double) -> Bool {
guard balance >= amount else { return false }
balance -= amount
return true
}
var currentBalance: Double {
return balance
}
}
// Использование
let account = BankAccount()
Task {
await account.deposit(100.0)
let balance = await account.currentBalance
}
Компилятор гарантирует, что доступ к свойствам и методам actor происходит изолированно.
Критерии выбора механизма:
- Производительность: Для частого чтения — read-write lock или concurrent queue с барьерами.
- Безопасность: Акторы и очереди безопаснее ручных блокировок.
- Deadlock avoidance: Избегайте вложенных блокировок, используйте
.barrierили actors. - Уровень абстракции: Высокоуровневые решения (DispatchQueue, Actor) предпочтительнее низкоуровневых (pthread_mutex).
- Поддержка Swift Concurrency: Для новых проектов с поддержкой iOS 13+ — Actor и @MainActor для UI.
Основная рекомендация — использовать серийные очереди для простых случаев и акторы для сложных моделей данных в современных приложениях, так как они интегрированы в систему типов Swift и исключают множество ошибок времени выполнения.