Логотип YeaHub

База вопросов

Собеседования

Тренажёр

База ресурсов

Обучение

Навыки

Задачи

Войти

Выбери, каким будет IT завтра — вместе c нами!

YeaHub — это полностью открытый проект, призванный объединить и улучшить IT-сферу. Наш исходный код доступен для просмотра на GitHub. Дизайн проекта также открыт для ознакомления в Figma.

© 2026 YeaHub

AI info

Карта сайта

Документы

Медиа

Назад
Вопрос про Swift : gcd, dispatchbarrier

Как работает barrier в GCD и когда его стоит использовать?

Вопрос проверяет понимание конкурентного доступа к данным, особенностей DispatchQueue и умение выбирать подходящий механизм синхронизации для сценариев «много чтений — мало записей».

Короткий ответ

barrier в GCD — это специальная задача, которая на concurrent queue выполняется эксклюзивно: она ждёт завершения всех ранее запланированных задач и блокирует выполнение последующих, пока сама не закончится. Это удобно для потокобезопасной записи при параллельных чтениях. Обычно barrier используют в структурах вроде кешей или хранилищ, где чтений много, а записей мало. На serial queue barrier не даёт преимуществ, потому что там и так всё выполняется по очереди.

Длинный ответ

DispatchBarrier — это способ построить поведение, похожее на «read-write lock», но средствами GCD. Он особенно полезен, когда хочется разрешить параллельные чтения и при этом обеспечить эксклюзивную запись.

Что такое barrier

Определение: barrier — это задача, которая на concurrent queue выполняется только тогда, когда завершились все задачи перед ней, и во время её выполнения никакие другие задачи из этой очереди не выполняются.

Важно: это правило относится именно к одной конкретной очереди.

Как barrier работает на concurrent queue

Представим очередь и задачи в ней:

  1. read A (обычная async)

  2. read B (обычная async)

  3. write X (async(flags: .barrier))

  4. read C (обычная async)

Поведение будет таким:

  1. read A и read B могут выполняться параллельно.

  2. write X начнётся только после завершения read A и read B.

  3. Пока выполняется write X, read C не начнётся.

  4. После завершения write X чтения снова могут выполняться параллельно.

То есть barrier создаёт «точку эксклюзивности» для записи.

Почему barrier часто используют для защиты данных

В многопоточном коде популярный паттерн — «много чтений, мало записей».
Например:

  • кеш картинок

  • словарь с результатами вычислений

  • локальный in-memory storage

Если сделать всё через serial queue, то:

  • и чтения, и записи будут строго по очереди

  • это просто, но может стать узким местом

С barrier можно:

  • разрешить параллельные чтения

  • обеспечить безопасные записи

Пример: потокобезопасный кеш на Dictionary

Ниже пример обёртки, где чтения параллельные, а записи эксклюзивные:

final class ThreadSafeCache<Key: Hashable, Value> {
    private var storage: [Key: Value] = [:]
    private let queue = DispatchQueue(label: "cache.queue", attributes: .concurrent)

    func get(_ key: Key) -> Value? {
        queue.sync {
            storage[key]
        }
    }

    func set(_ value: Value, for key: Key) {
        queue.async(flags: .barrier) {
            self.storage[key] = value
        }
    }

    func remove(_ key: Key) {
        queue.async(flags: .barrier) {
            self.storage.removeValue(forKey: key)
        }
    }
}

Почему get через sync

Тут важно разделить два понятия:

  • параллельность выполнения очереди (concurrent queue)

  • тип вызова (sync / async)

get обычно нужен «прямо сейчас», поэтому делаем sync, чтобы вернуть значение сразу. При этом чтения из concurrent queue могут выполняться параллельно (если несколько потоков одновременно вызывают get).

Почему записи через async(flags: .barrier)

Запись можно делать асинхронно:

  • она защищена barrier

  • она не блокирует вызывающий поток

  • порядок записей относительно других задач очереди сохраняется

Если нужно, чтобы set завершился до продолжения кода, можно сделать queue.sync(flags: .barrier), но это увеличит риск блокировок.

Важные ограничения и частые ошибки

1) Barrier работает корректно только на вашей очереди

barrier гарантирует эксклюзивность только относительно задач, поставленных в эту же очередь.

Если кто-то меняет storage напрямую или через другую очередь — защита ломается. Поэтому:

  • состояние должно быть инкапсулировано

  • доступ к нему должен идти только через одну очередь

2) Barrier почти бессмысленен на global queue

На DispatchQueue.global() вы не контролируете, что ещё туда ставит система и другие части программы. Поэтому для корректного сценария «read/write» почти всегда создают собственную concurrent queue.

3) Barrier не заменяет правильную архитектуру

Если состояние лучше вообще не шарить — лучше не шарить.
Например, иногда вместо shared dictionary лучше:

  • передавать данные по копии (value semantics)

  • использовать actor (если проект на Swift Concurrency)

  • выносить работу в отдельный слой, который сериализует доступ

4) Deadlock из-за неправильного sync

Самая частая ошибка — вызвать queue.sync внутри задачи, которая уже выполняется на queue. Это приведёт к взаимной блокировке.

Плохой пример (упрощённо):

queue.async {
    let x = queue.sync { storage["a"] } // риск deadlock
}

Правило:

  • не вызывать sync на той же очереди, на которой вы уже находитесь

Когда barrier стоит использовать

Используйте barrier, если выполняются условия:

  1. Есть общее состояние (например, словарь/массив в памяти).

  2. Очень много операций чтения.

  3. Записи редкие, но должны быть безопасными.

  4. Вы хотите повысить параллелизм по сравнению с serial queue.

Типичные кейсы:

  • кеши

  • хранилища конфигурации

  • карты соответствий / справочники

  • дедупликация запросов (храните in-flight операции и очищаете по завершению)

Когда barrier лучше не использовать

Есть сценарии, где лучше выбрать другой подход:

  1. Записей много и они «тяжёлые»

    • barrier будет часто блокировать чтения, выгода исчезнет

  2. Нужно сложное ожидание/координация задач

    • проще использовать OperationQueue или async/await

  3. Есть риск обратиться к данным «мимо очереди»

    • лучше serial queue или actor, чтобы проще гарантировать безопасность

Краткий вывод

DispatchBarrier — хороший инструмент для паттерна «параллельные чтения + эксклюзивные записи» на собственной concurrent queue. Он даёт больше производительности, чем serial queue, но требует дисциплины: всё состояние должно быть скрыто и доступно только через эту очередь.

  • Аватар

    iOS Guru

    Roman Isakov

    Guru – это эксперты YeaHub, которые помогают развивать комьюнити.

Уровень

  • Рейтинг:

    5

  • Сложность:

    8

Навыки

  • Swift

    Swift

Ключевые слова

#gcd

#dispatchbarrier

Подпишись на iOS Developer в телеграм

  • Аватар

    iOS Guru

    Roman Isakov

    Guru – это эксперты YeaHub, которые помогают развивать комьюнити.