Какие преимущества и недостатки у Grand Central Dispatch (GCD) в iOS/macOS?

«Какие преимущества и недостатки у Grand Central Dispatch (GCD) в iOS/macOS?» — вопрос из категории Многопоточность, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Преимущества:

  • Простой API высокого уровня: Позволяет легко описывать асинхронные задачи без прямого управления потоками.
  • Автоматическое управление пулом потоков: Система сама создает, переиспользует и уничтожает потоки, оптимизируя использование ресурсов.
  • Интеграция с системой: Тесно связана с низкоуровневым ядром Darwin, что обеспечивает высокую эффективность и низкие накладные расходы.
  • Поддержка приоритетов QoS (Quality of Service): Позволяет классифицировать задачи (.userInteractive, .utility, .background), помогая системе оптимизировать производительность и энергопотребление.

Недостатки:

  • Сложность отладки: Асинхронный код усложняет трассировку стека вызовов и поиск причин ошибок. Могут возникать race conditions.
  • Риск взаимоблокировок (deadlock): При синхронной отправке (sync) задач на текущую или serial очередь.
  • Отсутствие отмены задач "из коробки": Операции в DispatchQueue нельзя отменить после отправки, в отличие от Operation.
  • Ограниченный контроль: По сравнению с низкоуровневыми API (pthreads) меньше возможностей для тонкой настройки.

Примеры использования:

// 1. Параллельная обработка массива с использованием concurrent очереди
let concurrentQueue = DispatchQueue(label: "com.example.processing", attributes: .concurrent)
let group = DispatchGroup()

for item in largeArray {
    group.enter()
    concurrentQueue.async {
        process(item) // Ресурсоемкая операция
        group.leave()
    }
}

// Ожидание завершения всех задач
group.notify(queue: .main) {
    print("All items processed")
    self.updateUI()
}

// 2. Избегание deadlock: НИКОГДА не вызывайте sync на текущей serial очереди
let serialQueue = DispatchQueue(label: "com.example.serial")
serialQueue.async {
    // Это вызовет deadlock:
    // serialQueue.sync { print("This will never print") }

    // Вместо этого используйте async
    serialQueue.async { print("This is safe") }
}

// 3. Использование барьера для безопасной записи в concurrent очереди
class ThreadSafeDictionary<Key: Hashable, Value> {
    private var storage = [Key: Value]()
    private let queue = DispatchQueue(label: "com.example.dict", attributes: .concurrent)

    func set(_ value: Value, for key: Key) {
        queue.async(flags: .barrier) { // Барьер гарантирует эксклюзивный доступ
            self.storage[key] = value
        }
    }

    func get(for key: Key) -> Value? {
        queue.sync { // Concurrent чтение безопасно
            return storage[key]
        }
    }
}