Что произойдет при синхронном добавлении задачи на глобальную параллельную очередь?

«Что произойдет при синхронном добавлении задачи на глобальную параллельную очередь?» — вопрос из категории Многопоточность, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

Детали и пример: Метод DispatchQueue.global().sync выполняет две ключевые операции:

  1. Отправляет блок кода на выполнение в глобальную параллельную очередь.
  2. Немедленно блокирует текущий поток, в котором был вызван .sync, и ждет, пока переданный блок не выполнится полностью.
print("1. До sync на главном потоке")

DispatchQueue.global().sync {
    print("2. Внутри sync блока. Выполняется в потоке глобальной очереди: (Thread.current)")
    sleep(2) // Имитация долгой работы
    print("3. Синхронный блок завершен")
}

print("4. После sync. Главный поток разблокирован.")

Вывод будет строго последовательным:

1. До sync на главном потоке
2. Внутри sync блока. Выполняется в потоке глобальной очереди: <NSThread: 0x...>{number = 4, name = (null)}
3. Синхронный блок завершен
4. После sync. Главный поток разблокирован.

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

  • Дедлок на главном потоке: Вызов DispatchQueue.main.sync из главного потока приведет к гарантированному дедлоку, так как поток пытается заблокировать сам себя.
  • Цель использования .sync:
    • Для обеспечения thread-safety при доступе к общему ресурсу.
    • Когда необходимо получить результат выполнения задачи прежде, чем продолжить работу в текущем потоке.
  • Альтернатива: Вместо global().sync для простой синхронизации часто используют serial очередь.

    let threadSafeQueue = DispatchQueue(label: "com.threadsafe.queue")
    var sharedValue = 0
    
    threadSafeQueue.sync {
        // Безопасное чтение/запись sharedValue
        sharedValue += 1
    }
  • Отличие от .async: Метод .async немедленно возвращает управление вызывающему потоку, не дожидаясь выполнения блока.