Зачем нужен флаг barrier для синхронного исполнения на параллельной DispatchQueue
Что проверяют
Умеете ли вы защитить общее состояние на параллельной очереди так, чтобы чтения шли одновременно, а запись выполнялась в одиночку. Следом обычно спрашивают, чем sync с барьером отличается от async с барьером и почему барьер на глобальной очереди ничего не делает.
Как отвечать
Флаг .barrier из DispatchWorkItemFlags превращает задачу на параллельной очереди в эксклюзивную. Очередь дожидается окончания всех задач, отправленных раньше, выполняет барьерную задачу одну и только потом снова запускает следующие параллельно. На время барьера параллельная очередь ведёт себя как последовательная.
Классическое применение это схема «много читателей, один писатель»:
- чтение:
queue.sync { storage[key] }. Синхронно, потому что значение нужно вернуть вызывающему, и параллельно с другими чтениями; - запись:
queue.async(flags: .barrier) { storage[key] = value }. Вызывающий не ждёт, а очередь гарантирует, что во время записи никто не читает; sync(flags: .barrier): нужен, когда вызывающему важно дождаться окончания записи, например перед тем как сразу прочитать результат. Цена в том, что вызывающий поток блокируется, пока не закончатся все чтения, стоящие впереди.
Главное условие: барьер работает только на своей параллельной очереди, созданной как DispatchQueue(label: "cache", attributes: .concurrent). На последовательной очереди он бессмыслен, а на DispatchQueue.global() игнорируется, и задача выполняется как обычная.
Хорошо показать, что знаете альтернативы: actor в Swift Concurrency, OSAllocatedUnfairLock с iOS 16, Mutex из фреймворка Synchronization с iOS 18. Для простого словаря под нагрузкой блокировка обычно дешевле очереди.
Где спотыкаются
Ставят барьер на глобальную очередь. Код компилируется и работает, но защиты нет, и гонка всплывает редким крашем в проде.
Вызывают queue.sync изнутри задачи на той же очереди, например запись из обработчика чтения. Получается взаимоблокировка: очередь ждёт сама себя.
Защищают запись барьером, а читают свойство напрямую, мимо очереди. Барьер упорядочивает только задачи этой очереди, всё остальное продолжает гонку.