← Назад к вопросам

Как решить Race Condition с использованием GCD?

2.0 Middle🔥 191 комментариев
#Многопоточность и асинхронность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Race Condition и решение с помощью GCD

Что такое Race Condition

Race condition — это ситуация, когда результат кода зависит от времени выполнения потоков. Два потока пытаются получить доступ к одному ресурсу одновременно, что приводит к непредсказуемому поведению.

Пример Race Condition

class Counter {
    var count = 0
    
    func increment() {
        let temp = self.count
        // Другой поток может изменить self.count здесь
        self.count = temp + 1
    }
}

let counter = Counter()

for _ in 0..<1000 {
    DispatchQueue.global().async {
        counter.increment()  // Race condition!
    }
}

print(counter.count)  // Может быть <1000!
// Expected: 1000
// Actual: 987, 995, 1000... (случайно)

Решение 1: Serial Queue

class Counter {
    private var count = 0
    private let queue = DispatchQueue(label: "com.example.counter")
    
    func increment() {
        queue.async {  // Serial queue гарантирует порядок
            self.count += 1
        }
    }
    
    func getCount() -> Int {
        var result = 0
        queue.sync {  // Ждём завершения
            result = self.count
        }
        return result
    }
}

let counter = Counter()

for _ in 0..<1000 {
    DispatchQueue.global().async {
        counter.increment()  // Safe!
    }
}

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
    print(counter.getCount())  // Всегда 1000
}

Решение 2: Concurrent Queue с Barrier

class DataStore {
    private var data: [String: Any] = [:]
    private let queue = DispatchQueue(
        label: "com.example.store",
        attributes: .concurrent
    )
    
    func set(key: String, value: Any) {
        queue.async(flags: .barrier) {  // Exclusive write
            self.data[key] = value
        }
    }
    
    func get(key: String) -> Any? {
        var result: Any?
        queue.sync {  // Concurrent read
            result = self.data[key]
        }
        return result
    }
}

Как работает barrier:

  • Read операции выполняются параллельно
  • Write операции выполняются эксклюзивно
  • Барьер ждёт завершения всех текущих задач перед выполнением

Решение 3: DispatchSemaphore

class Lock {
    private let semaphore = DispatchSemaphore(value: 1)  // Binary semaphore
    private var count = 0
    
    func increment() {
        semaphore.wait()  // Захватываем
        defer { semaphore.signal() }  // Освобождаем
        
        self.count += 1
    }
    
    func getCount() -> Int {
        semaphore.wait()
        defer { semaphore.signal() }
        return self.count
    }
}

Решение 4: NSLock

class ThreadSafeCounter {
    private var count = 0
    private let lock = NSLock()
    
    func increment() {
        lock.lock()
        defer { lock.unlock() }
        
        self.count += 1
    }
    
    func getCount() -> Int {
        lock.lock()
        defer { lock.unlock() }
        return self.count
    }
}

Решение 5: Swift Actor (современный подход)

actor CounterActor {
    private var count = 0
    
    func increment() {
        self.count += 1
    }
    
    func getCount() -> Int {
        self.count
    }
}

let counter = CounterActor()
await counter.increment()  // Автоматически синхронизировано
let result = await counter.getCount()
print(result)

Сравнение подходов

ПодходПроизводительностьСложностьРекомендация
Serial QueueХорошоСредняя✅ Для простых случаев
BarrierОтличнаяСредняя✅ Для read/write
SemaphoreХорошоСложная⚠️ Низкоуровневое
NSLockХорошоПростая⚠️ Риск deadlock
ActorОтличнаяПростая✅ Swift 5.5+

Практический пример: Thread-safe Database

class UserDatabase {
    private var users: [String: User] = [:]
    private let queue = DispatchQueue(
        label: "com.example.db",
        attributes: .concurrent
    )
    
    func saveUser(_ user: User) {
        queue.async(flags: .barrier) {
            self.users[user.id] = user
        }
    }
    
    func getUser(id: String) -> User? {
        var result: User?
        queue.sync {
            result = self.users[id]
        }
        return result
    }
    
    func getAllUsers() -> [User] {
        var result: [User] = []
        queue.sync {
            result = Array(self.users.values)
        }
        return result
    }
}

Обнаружение Race Conditions

Thread Sanitizer в Xcode:

  1. Edit Scheme → Run → Diagnostics
  2. Включи "Thread Sanitizer"
  3. Запусти приложение
  4. Ловит race conditions автоматически

Ключевые правила

✅ Используй Serial Queue для синхронизации ✅ Используй Barrier для read/write операций ✅ Используй Actor в современном коде ✅ Всегда устанавливай defer для unlock ❌ Никогда не пренебрегай синхронизацией ❌ Не используй NSLock если можно использовать Queue

Race conditions — коварная проблема. Правильная синхронизация критична для надёжности приложения.