Каковы последствия вызова sync на concurrent DispatchQueue?

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

Ответ

Вызов sync (синхронной операции) на concurrent (параллельной) очереди приводит к последовательному выполнению переданной задачи в текущем потоке, при этом параллельное выполнение других задач в этой очереди не блокируется.

Ключевое поведение:

  1. Блокировка вызывающего потока: Текущий поток блокируется до полного завершения задачи, переданной в sync.
  2. Задача выполняется НЕ в текущем потоке: Система может выбрать любой поток из пула для выполнения этой задачи, но логика sync обеспечивает ожидание.
  3. Concurrent-природа очереди сохраняется: Другие задачи, отправленные в эту же очередь через async, могут выполняться параллельно друг с другом и с задачей внутри sync.

Пример и важное предупреждение:

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

print("Начало на главном потоке")

// Эта sync-задача заблокирует главный поток на 2 секунды.
concurrentQueue.sync {
    print("Sync Task 1 началась в (Thread.current)")
    sleep(2)
    print("Sync Task 1 завершилась")
}

// Эта async-задача будет поставлена в очередь и может выполняться параллельно.
concurrentQueue.async {
    print("Async Task 2 началась в (Thread.current)")
    sleep(1)
    print("Async Task 2 завершилась")
}

print("Конец на главном потоке (после sync)")
// Возможный вывод:
// Начало на главном потоке
// Sync Task 1 началась в <NSThread: ...>{number = 1, name = main}
// Sync Task 1 завершилась
// Конец на главном потоке (после sync)
// Async Task 2 началась в <NSThread: ...>{number = 5, name = (null)}
// Async Task 2 завершилась

Главная опасность — взаимная блокировка (deadlock):

concurrentQueue.sync {
    // ВНИМАНИЕ: Вызов sync на ТОЙ ЖЕ очереди изнутри себя приводит к deadlock.
    concurrentQueue.sync { // Поток заблокирован на внешнем sync и ждет этот внутренний, который никогда не начнется.
        print("Этот код никогда не выполнится")
    }
}

Вывод: Используйте sync на concurrent очередях с осторожностью, только для простых, быстрых операций синхронизации доступа к общим ресурсам (например, с использованием .sync(flags: .barrier)), и избегайте вложенных вызовов sync на той же очереди.