DispatchQueue: що це, черга GCD та основи багатопоточності

Автор: IT Sectr Опубліковано: 2026-03-16 Час читання: 8 хв

DispatchQueue — це фундаментальна черга Grand Central Dispatch (GCD) для керування асинхронними завданнями в iOS та macOS. За даними Apple Developer Documentation, 2026, DispatchQueue абстрагує керування потоками від розробника через serial та concurrent черги. GCD автоматично розподіляє завдання по системному пулу потоків, позбавляючи від ручного створення та знищення потоків.

Головне

  • DispatchQueue — основна абстракція GCD для асинхронного виконання коду в iOS
  • Serial черга виконує завдання строго послідовно, виключаючи стан гонки
  • Concurrent черга запускає кілька завдань паралельно через системний пул потоків
  • QoS задає пріоритет завдання — від userInteractive до background
  • DispatchQueue.main — єдина черга для оновлення UIKit на головному потоці

Що таке DispatchQueue та Grand Central Dispatch

DispatchQueue — це об'єкт фреймворку Grand Central Dispatch (GCD), який керує виконанням завдань у системних або кастомних чергах потоків. Grand Central Dispatch — низькорівнева бібліотека Apple, доступна з iOS 4 та macOS 10.6, яка повністю абстрагує керування потоками від розробника. GCD використовує пул потоків операційної системи та автоматично масштабує кількість потоків під навантаження пристрою.

Розробнику не потрібно створювати та знищувати потоки вручну — GCD бере це завдання на себе, надаючи простий API через DispatchQueue. Завдання у вигляді замикання (closure) відправляється в чергу через методи sync або async. У першому випадку викликаючий потік блокується до завершення завдання, у другому — виконання продовжується негайно.

За даними Apple (2026), GCD використовує системний пул потоків, який адаптується під кількість ядер та поточне навантаження процесора. Concurrent черга не створює новий потік на кожне завдання — GCD перевикористовує потоки з пулу, що мінімізує накладні витрати на створення потоків.

Архітектура GCD

Grand Central Dispatch складається з трьох ключових компонентів: черги (DispatchQueue), групи (DispatchGroup) та семафори (DispatchSemaphore). Черга — основний елемент, що приймає завдання у вигляді блоків коду. DispatchGroup синхронізує виконання кількох завдань, а DispatchSemaphore обмежує доступ до спільного ресурсу певною кількістю потоків.

Кожна черга GCD пов'язана з певним QoS (Quality of Service) класом, який повідомляє системі про важливість завдання. Система використовує QoS для розподілу процесорного часу між чергами, віддаючи пріоритет більш критичним завданням — наприклад, оновленню UI або обробці дотиків користувача.

Serial та Concurrent черги: порівняння

Serial черга виконує завдання строго послідовно, одне за одним. Якщо в serial чергу помістити три завдання, друге почнеться тільки після повного завершення першого. Serial черги застосовуються для синхронізації доступу до спільних ресурсів — наприклад, до масиву, який змінюється з кількох частин коду.

Concurrent черга запускає кілька завдань одночасно, розподіляючи їх по доступних потоках із системного пулу. Завдання на concurrent черзі запускаються в порядку надходження (FIFO), але завершуються в довільному порядку, якщо час їх виконання різниться. Concurrent черга не гарантує порядку завершення — тільки порядок запуску.

ПараметрSerial чергаConcurrent черга
Порядок виконанняСтрого послідовнийПаралельний
Кількість потоківОдинДекілька з пулу GCD
ЗастосуванняЗахист спільних ресурсівНезалежні обчислення
Main queueТак (головний потік)Ні
Ризик deadlockВисокий при sync на тій самій черзіНизький

Коли вибирати serial чергу

Serial черга ідеальна для завдань, що модифікують спільний стан — запис у файл, оновлення моделі даних або робота з Core Data. Використання serial черги гарантує, що дві ділянки коду не змінять одні й ті самі дані одночасно, виключаючи стан гонки без додаткових блокувань.

Коли вибирати concurrent чергу

Concurrent черга підходить для завдань, що не залежать одне від одного: завантаження кількох зображень, паралельні мережеві запити або пакетна обробка даних. GCD автоматично вирішує, скільки завдань запустити одночасно, виходячи з кількості ядер процесора та поточного завантаження системи.

Quality of Service: пріоритети виконання завдань

QoS (Quality of Service) — механізм GCD, який повідомляє операційній системі про важливість та терміновість завдання. Система використовує QoS для планування потоків: завдання з вищим QoS отримують більше процесорного часу та запускаються раніше. Значення QoS передається при створенні черги або відправленні конкретного завдання.

У GCD доступно п'ять класів QoS. .userInteractive — найвищий пріоритет для завдань, пов'язаних з UI. .userInitiated — для завдань, ініційованих користувачем. .utility — для фонових завдань з відображенням прогресу. .background — для завдань, невидимих користувачеві. .default — проміжний рівень між userInitiated та utility, використовується за замовчуванням.

За даними Apple (2026), неправильний вибір QoS — одна з частих причин проблем з продуктивністю. Запуск фонового завантаження з QoS .userInteractive відбирає ресурси у UI, викликаючи мікро-лага в анімаціях. Рекомендується вибирати найнижчий QoS, який все ще забезпечує прийнятний час виконання.

Приклад застосування QoS

При завантаженні зображення для негайного відображення використовуйте .userInitiated — користувач очікує результат. Для попереднього завантаження наступного екрану достатньо .utility. Фонова синхронізація з сервером виконується з .background, мінімізуючи вплив на активні завдання.

DispatchGroup та семафори: синхронізація завдань

DispatchGroup дозволяє відстежувати завершення групи завдань. Коли всі завдання в групі завершені, GCD викликає обробник notify на вказаній черзі. Це особливо корисно при завантаженні кількох незалежних ресурсів — даних профілю, списку друзів та налаштувань, коли інтерфейс потрібно оновити тільки після отримання всіх даних.

DispatchGroup підтримує синхронний виклик wait(), який блокує поточний потік до завершення всіх завдань. Це зручно, коли код не може продовжуватися без результатів групи. Асинхронний варіант — notify() — викликає замикання на вказаній черзі після завершення всіх завдань, не блокуючи викликаючий потік.

DispatchSemaphore для обмеження паралелізму

DispatchSemaphore контролює доступ до ресурсу, обмежуючи кількість одночасних звернень. Семафор з початковим значенням 3 дозволяє запустити не більше трьох паралельних завдань. При виклику wait() лічильник зменшується, при signal() — збільшується. Якщо лічильник дорівнює нулю, потік блокується до звільнення ресурсу.

Приклади коду з DispatchQueue в Swift

Розглянемо три практичних приклади використання DispatchQueue в Swift. Перший демонструє базовий async виклик з поверненням на головний потік, другий — синхронізацію через serial чергу, третій — DispatchGroup для паралельних запитів.

Базовий async виклик з поверненням на main

DispatchQueue.main — це serial черга головного потоку, призначена виключно для UI-операцій. Завжди використовуйте її для оновлення інтерфейсу після завершення фонової роботи.

swift
let queue = DispatchQueue.global(qos: .userInitiated)
queue.async {
    let data = self.fetchData()
    DispatchQueue.main.async {
        self.updateUI(with: data)
    }
}

Serial черга для захисту спільного ресурсу

Створення власної serial черги з унікальним ідентифікатором синхронізує доступ до змінюваного масиву. Всі операції читання та запису проходять через одну чергу, виключаючи стан гонки.

swift
let serialQueue = DispatchQueue(label: "com.app.items")
var items: [Int] = []

serialQueue.async {
    items.append(1)
}
serialQueue.async {
    let last = items.last
    DispatchQueue.main.async {
        print("Last item: \(last)")
    }
}

DispatchGroup для паралельних запитів

DispatchGroup дозволяє запустити кілька завдань на concurrent черзі та отримати повідомлення про завершення всіх. Це корисно при завантаженні даних для екрану профілю.

swift
let group = DispatchGroup()
let worker = DispatchQueue.global()

worker.async(group: group) { self.loadProfile() }
worker.async(group: group) { self.loadFriends() }
worker.async(group: group) { self.loadSettings() }

group.notify(queue: DispatchQueue.main) {
    self.showCompleteUI()
}

Типові помилки при роботі з DispatchQueue

Deadlock при sync виклику на serial черзі — найпоширеніша помилка. Якщо завдання на serial черзі викликає queue.sync на тій самій черзі, потік блокується назавжди. Черга чекає завершення поточного завдання, а завдання чекає завершення sync-виклику — класичне взаємне блокування.

Оновлення UI з фонового потоку

Всі операції з UIKit повинні виконуватися на головному потоці. Xcode виявляє такі помилки в Debug через Main Thread Checker. У Release-збірці вони призводять до непередбачуваної поведінки: анімації не запускаються, UI не оновлюється, можливі креші.

Надмірне створення власних черг

Створення сотень custom-черг замість глобальних черг — антипатерн. Кожна черга споживає ресурси системи. Для більшості завдань достатньо глобальних concurrent черг з різним QoS та однієї-двох serial черг для синхронізації спільних даних.

Ігнорування autoreleasepool в циклах

При виконанні ресурсоємних циклічних завдань на фоновій черзі без autoreleasepool пам'ять зростає до завершення всього циклу. ARC звільняє об'єкти тільки при виході з autorelease pool. Загортайте ітерації циклу в autoreleasepool { } для своєчасного звільнення пам'яті.

Часто задавані питання

У чому різниця між DispatchQueue та OperationQueue?

OperationQueue побудована поверх GCD, але надає більш високорівневий API з залежностями операцій, KVO та підтримкою скасування. DispatchQueue — низькорівнева черга для простих async завдань без керування залежностями.

Чи можна примусово зупинити завдання в DispatchQueue?

GCD не підтримує зупинку запущеного завдання. Метод suspend() призупиняє тільки нові завдання, поточне виконується до кінця. Для скасування потрібна ручна перевірка прапорця всередині коду завдання.

Який QoS вибрати для мережевого запиту?

Для основного запиту з негайним відображенням результату — .userInitiated. Для попереднього завантаження даних — .utility. Для фонової синхронізації — .background.

Скільки потоків використовує concurrent черга?

GCD не фіксує кількість потоків. Пул потоків динамічно масштабується під навантаження, враховуючи ядра процесора, поточне завантаження та QoS кожного завдання. Максимальна кількість обмежена системою.

Чому DispatchQueue.main обов'язковий для UIKit?

UIKit не є потокобезпечним — всі його класи повинні викликатися тільки з головного потоку. Порушення викликає непередбачувану поведінку, пропущені оновлення та креші в Production.

Підсумки

  • DispatchQueue — основний інструмент Grand Central Dispatch для асинхронних завдань в iOS та macOS
  • Serial черга виконує завдання послідовно, виключаючи стан гонки без блокувань
  • Concurrent черга запускає завдання паралельно через системний пул потоків
  • QoS визначає пріоритет завдання — від userInteractive до background
  • DispatchGroup синхронізує кілька паралельних завдань з notify на головному потоці
  • Deadlock при sync на зайнятій serial черзі — критична помилка, що вимагає уваги
  • Main thread обов'язковий для UIKit — оновлюйте інтерфейс тільки через DispatchQueue.main

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також