Всегда ли результат выполнения кода с асинхронными очередями в Swift будет одинаковым
Что проверяют
Понимаете ли вы, какие гарантии порядка дают очереди GCD, а какие нет. Обычно показывают фрагмент с несколькими print и async и просят предсказать вывод, а следом спрашивают, как защитить общее состояние.
Как отвечать
Короткий ответ: нет, не всегда. Дальше разложите, от чего это зависит:
- последовательная очередь: задачи выполняются по одной, строго в порядке постановки. Порядок между задачами одной serial-очереди гарантирован;
- конкурентная очередь: задачи стартуют в порядке постановки, но идут параллельно и заканчиваются в любом порядке;
asyncпротивsync:asyncвозвращает управление сразу, поэтому код после него может выполниться раньше блока.syncждёт завершения блока.
Классический пример: на главном потоке print(1), DispatchQueue.main.async { print(2) }, print(3). Вывод всегда 1, 3, 2: блок встаёт в главную очередь и выполнится, когда текущий код отпустит поток. Если заменить главную очередь на DispatchQueue.global().async, возможны и 1, 2, 3, и 1, 3, 2.
Если несколько задач меняют общую переменную без синхронизации, это гонка данных, и результат может отличаться от запуска к запуску. Защита: менять состояние только на одной serial-очереди, барьер .barrier на собственной конкурентной очереди, NSLock или OSAllocatedUnfairLock. В Swift Concurrency состояние прячут в actor, а режим Swift 6 ловит многие гонки ещё при компиляции. В рантайме гонки ищет Thread Sanitizer в Xcode.
Где спотыкаются
Путают асинхронность с параллельностью. async на последовательную очередь ничего не распараллеливает, он только не блокирует вызывающий поток.
Ставят барьер на глобальную очередь. Там .barrier не действует и блок выполняется как обычный: барьер работает только на своей очереди с атрибутом .concurrent.
Вызывают DispatchQueue.main.sync с главного потока или sync на serial-очередь изнутри неё самой. Это взаимная блокировка, приложение просто зависает.