← Назад к вопросам
Как решить 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:
- Edit Scheme → Run → Diagnostics
- Включи "Thread Sanitizer"
- Запусти приложение
- Ловит race conditions автоматически
Ключевые правила
✅ Используй Serial Queue для синхронизации ✅ Используй Barrier для read/write операций ✅ Используй Actor в современном коде ✅ Всегда устанавливай defer для unlock ❌ Никогда не пренебрегай синхронизацией ❌ Не используй NSLock если можно использовать Queue
Race conditions — коварная проблема. Правильная синхронизация критична для надёжности приложения.