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

Как работает Sync в Concurrent очереди?

2.0 Middle🔥 202 комментариев
#Многопоточность и асинхронность#Язык Swift

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

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

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

Как работает sync на Concurrent очереди в GCD

sync (синхронная) операция на concurrent (параллельной) очереди в Grand Central Dispatch имеет специфическое поведение, которое важно понимать для написания корректного многопоточного кода.

Основной принцип работы

Когда вы вызываете DispatchQueue.sync на concurrent очереди:

  1. Текущий поток блокируется до полного выполнения переданной задачи
  2. Задача выполняется на одном из доступных потоков пула потоков GCD
  3. Не создает deadlock сам по себе, в отличие от вызова sync на main очереди из main потока
let concurrentQueue = DispatchQueue(label: "com.example.concurrent", 
                                    attributes: .concurrent)

print("1. До sync")
concurrentQueue.sync {
    print("2. Внутри sync блока - выполняется на: \(Thread.current)")
    // Эта задача будет выполнена до продолжения основного потока
}
print("3. После sync - основной поток продолжается")

Ключевые особенности поведения

Блокировка вызывающего потока

Вызывающий поток приостанавливается до тех пор, пока задача не будет полностью выполнена:

let concurrentQueue = DispatchQueue(label: "test.concurrent", 
                                    qos: .userInitiated, 
                                    attributes: .concurrent)

print("Начало")
concurrentQueue.sync {
    // Эта операция займет 2 секунды
    Thread.sleep(forTimeInterval: 2)
    print("Долгая операция завершена")
}
// Сюда выполнение дойдет только через 2 секунды
print("Конец")

Не гарантирует порядок выполнения для множественных задач

Для concurrent очереди с async задачи выполняются параллельно, но с sync порядок вызова соответствует порядку выполнения:

let concurrentQueue = DispatchQueue(label: "example", attributes: .concurrent)

concurrentQueue.sync { print("Задача 1 - \(Thread.current)") } // Выполнится первой
concurrentQueue.sync { print("Задача 2 - \(Thread.current)") } // Выполнится второй
concurrentQueue.sync { print("Задача 3 - \(Thread.current)") } // Выполнится третьей
// Все три задачи выполнятся последовательно, несмотря на concurrent природу очереди

Использование существующих потоков

GCD использует пул потоков для выполнения задач:

let concurrentQueue = DispatchQueue.global(qos: .userInitiated)

// Может выполняться на разных потоках из пула
concurrentQueue.sync { print("Поток 1: \(Thread.current)") }
concurrentQueue.sync { print("Поток 2: \(Thread.current)") }
concurrentQueue.sync { print("Поток 3: \(Thread.current)") }

Сравнение с async на concurrent очереди

let concurrentQueue = DispatchQueue(label: "comparison", attributes: .concurrent)

print("=== Синхронное выполнение ===")
concurrentQueue.sync {
    for i in 1...3 {
        print("Sync \(i)")
        Thread.sleep(forTimeInterval: 0.1)
    }
}

print("=== Асинхронное выполнение ===")
concurrentQueue.async {
    for i in 1...3 {
        print("Async \(i)")
        Thread.sleep(forTimeInterval: 0.1)
    }
}

print("Основной поток продолжается сразу после async")
// Для async основной поток не блокируется

Практические сценарии использования

Синхронизация доступа к общим ресурсам

Хотя для thread-safe доступа лучше использовать serial очереди или акторы:

class SharedResource {
    private let concurrentQueue = DispatchQueue(label: "resource.queue", 
                                                attributes: .concurrent)
    private var internalValue: Int = 0
    
    var value: Int {
        get {
            concurrentQueue.sync {
                return internalValue
            }
        }
        set {
            concurrentQueue.sync(flags: .barrier) {
                internalValue = newValue
            }
        }
    }
}

Барьерные операции с sync

Для безопасной записи при параллельном чтении:

let concurrentQueue = DispatchQueue(label: "data.queue", 
                                    attributes: .concurrent)
private var dataArray: [String] = []

func readData() -> [String] {
    return concurrentQueue.sync {
        return dataArray
    }
}

func writeData(_ newData: String) {
    concurrentQueue.sync(flags: .barrier) {
        dataArray.append(newData)
    }
}

Важные предупреждения и best practices

Избегайте вложенных sync вызовов

// ОПАСНО: Может привести к deadlock
concurrentQueue.sync {
    concurrentQueue.sync { // Deadlock!
        print("Эта строка никогда не выполнится")
    }
}

Не блокируйте main поток

// ПЛОХО: Блокирует интерфейс
concurrentQueue.sync {
    // Долгая операция - UI зафризится
    Thread.sleep(forTimeInterval: 5)
}

// ХОРОШО: Используйте async для длительных операций
concurrentQueue.async {
    let result = performLongOperation()
    DispatchQueue.main.async {
        updateUI(with: result)
    }
}

Использование с обработкой ошибок

func fetchDataSynchronously() throws -> Data {
    var result: Result<Data, Error>?
    
    concurrentQueue.sync {
        do {
            let data = try performNetworkRequest()
            result = .success(data)
        } catch {
            result = .failure(error)
        }
    }
    
    return try result!.get()
}

Под капотом: как это работает

GCD управляет пулом потоков, и когда вы вызываете sync:

  1. Система проверяет доступные потоки
  2. Если есть свободный поток - задача выполняется на нем
  3. Если нет - может быть создан новый поток (в пределах ограничений системы)
  4. Вызывающий поток переходит в состояние ожидания
  5. После выполнения задачи поток разблокируется и продолжает работу

Когда использовать sync на concurrent очереди

  • Для thread-safe геттеров (быстрое чтение)
  • Когда нужно дождаться результата перед продолжением
  • Для синхронизации доступа к ресурсам с барьерными операциями
  • В unit-тестах для предсказуемого порядка выполнения

Вывод

sync на concurrent очереди обеспечивает синхронное выполнение с блокировкой вызывающего потока, но использует параллельную природу очереди только когда вызывается из разных потоков одновременно. Это мощный инструмент, но требующий аккуратного использования, чтобы избежать deadlock и проблем с производительностью. В современном Swift-коде для многих сценариев использования sync на concurrent очередях предпочтительнее использовать async/await с акторами, которые предоставляют более безопасную и выразительную модель concurrency.

Как работает Sync в Concurrent очереди? | PrepBro