Как работают последовательные очереди и что происходит с блоками при dispatch_async
Что проверяют
Понимаете ли вы, что последовательная очередь это гарантия порядка и отсутствия параллелизма, а не отдельный поток, и что async делает с блоком и захваченными объектами. Следом обычно дают код с вложенными sync и async и просят сказать, что он напечатает и где зависнет.
Как отвечать
Последовательная очередь в GCD исполняет блоки по одному в порядке постановки. DispatchQueue(label:) без атрибутов создаёт именно такую, DispatchQueue.main тоже последовательная и привязана к главному потоку.
Что происходит при dispatch_async (в Swift queue.async { }):
- постановка: очередь забирает блок себе (в Objective-C это
Block_copy) и удерживает его вместе со всем захваченным, включаяself; - возврат: вызов возвращается сразу, вызывающий поток не ждёт;
- исполнение: когда предыдущий блок закончился, GCD запускает следующий на потоке из своего пула. Блоки одной очереди могут попасть на разные потоки, но никогда не идут одновременно;
- освобождение: после выполнения блок отпускается вместе с захваченными объектами.
Отсюда классическое применение: последовательная очередь как замок для общего состояния. Запись через async, чтение через sync, и гонок нет без явного мьютекса. Порядок гарантирован только внутри одной очереди: блоки из двух разных последовательных очередей могут идти параллельно.
Про QoS: если в очередь с низким приоритетом ставят блок с высоким, GCD временно поднимает приоритет того, что стоит впереди, чтобы не случилось инверсии.
В современном Swift защиту состояния берёт на себя actor, стоит сказать, когда вы предпочтёте его очереди.
Где спотыкаются
Вызывают sync на той же последовательной очереди, в которой уже находятся, например DispatchQueue.main.sync из главного потока. Новый блок ждёт завершения текущего, текущий ждёт новый: дедлок, который libdispatch обычно обрывает крашем.
Считают, что у каждой последовательной очереди свой поток. Потоки берутся из общего пула, и сотня очередей не означает сотню потоков.
Забывают про захват: self удерживается, пока блок стоит в очереди. Для долгих задач нужен [weak self], иначе контроллер живёт дольше своего экрана.