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