Детальный обзор различных очередей и их свойств в GCD

Очереди в Grand Central Dispatch (GCD) — это основа многопоточного программирования в iOS. Понимание различных типов очередей и их характеристик позволяет создавать эффективные и отзывчивые приложения. В этой статье мы глубоко рассмотрим все виды очередей GCD и научимся выбирать оптимальный вариант для конкретных задач.


Фундаментальные типы очередей в GCD

GCD предоставляет два основных типа очередей: последовательные (serial) и параллельные (concurrent). Каждый тип имеет свои уникальные характеристики и области применения.

Последовательные очереди (Serial Queues)

Последовательные очереди выполняют только одну задачу за раз, в порядке FIFO (First-In-First-Out). Новая задача начинается только после завершения предыдущей.

// Создание пользовательской последовательной очереди
let serialQueue = DispatchQueue(label: "com.myapp.serialQueue")

// Добавление задач
serialQueue.async {
    print("Задача 1 запущена")
    // Будет выполнена полностью, прежде чем начнется Задача 2
    sleep(2)
    print("Задача 1 завершена")
}

serialQueue.async {
    print("Задача 2 запущена")
    sleep(1)
    print("Задача 2 завершена")
}

Ключевые свойства последовательных очередей:

  • Гарантированный порядок выполнения задач
  • Отсутствие гонок данных внутри очереди
  • Идеальны для доступа к общим ресурсам, требующим синхронизации

Параллельные очереди (Concurrent Queues)

Параллельные очереди могут выполнять несколько задач одновременно. Задачи запускаются в порядке FIFO, но могут завершаться в произвольном порядке.

// Создание пользовательской параллельной очереди
let concurrentQueue = DispatchQueue(label: "com.myapp.concurrentQueue", attributes: .concurrent)

// Добавление задач
concurrentQueue.async {
    print("Задача 1 запущена")
    sleep(2)
    print("Задача 1 завершена")
}

concurrentQueue.async {
    print("Задача 2 запущена")
    sleep(1)
    print("Задача 2 завершена") // Может завершиться раньше, чем Задача 1
}

Ключевые свойства параллельных очередей:

  • Одновременное выполнение нескольких задач
  • Максимальное использование доступных ресурсов CPU
  • Отсутствие гарантий порядка завершения задач

Системные очереди и их особенности

GCD предоставляет набор предопределенных глобальных очередей с различными приоритетами, а также специальную главную очередь.

Главная очередь (Main Queue)

let mainQueue = DispatchQueue.main

mainQueue.async {
    // Код, обновляющий пользовательский интерфейс
    updateUI()
}

Характеристики главной очереди:

  • Выполняется в главном потоке приложения
  • Является последовательной очередью
  • Предназначена исключительно для обновления UI и обработки пользовательских взаимодействий
  • Блокировка этой очереди приводит к зависанию интерфейса

Глобальные очереди с различными QoS (Quality of Service)

// Получение глобальных очередей с разными приоритетами
let userInteractiveQueue = DispatchQueue.global(qos: .userInteractive)
let userInitiatedQueue = DispatchQueue.global(qos: .userInitiated)
let defaultQueue = DispatchQueue.global(qos: .default)
let utilityQueue = DispatchQueue.global(qos: .utility)
let backgroundQueue = DispatchQueue.global(qos: .background)

QoS определяет приоритет задач:

  1. userInteractive — наивысший приоритет для задач, связанных с UI:

    • Анимации
    • Обработка жестов
    • Быстрые UI-обновления
  2. userInitiated — для задач, инициированных пользователем:

    • Открытие документа
    • Обработка клика по кнопке "Загрузить"
    • Задачи, требующие быстрой обратной связи
  3. default — стандартный приоритет:

    • Используется по умолчанию, если не указано иное
  4. utility — для длительных задач с индикатором прогресса:

    • Загрузка или импорт данных
    • Вычисления с отображением прогресса
  5. background — низший приоритет для незаметных пользователю задач:

    • Индексация
    • Синхронизация
    • Резервное копирование

Настройка пользовательских очередей

При создании собственных очередей GCD предлагает гибкие возможности для настройки их поведения.

Основные атрибуты при создании очереди

// Создание очереди с дополнительными атрибутами
let customQueue = DispatchQueue(
    label: "com.myapp.customQueue",          // Уникальный идентификатор
    qos: .userInitiated,                      // Приоритет
    attributes: [.concurrent, .initiallyInactive], // Дополнительные атрибуты
    autoreleaseFrequency: .workItem,          // Частота автоосвобождения
    target: DispatchQueue.global(.default)    // Целевая очередь
)

// Активация очереди
customQueue.activate()

Ключевые параметры настройки:

  • label — уникальный идентификатор очереди, полезный для отладки
  • qos — приоритет задач в очереди
  • attributes — дополнительные характеристики:
    • .concurrent — делает очередь параллельной
    • .initiallyInactive — создает неактивную очередь, требующую ручной активации
  • autoreleaseFrequency — частота автоосвобождения объектов Objective-C:
    • .inherit — наследует от целевой очереди
    • .workItem — освобождает пул после каждой задачи
    • .never — никогда не освобождает автоматически
  • target — целевая очередь, на которой будут выполняться задачи

Расширенные техники работы с очередями

Эффективная работа с очередями требует глубокого понимания их дополнительных возможностей.

Target Queues (Целевые очереди)

Целевые очереди позволяют создавать иерархии очередей с наследованием свойств.

let parentQueue = DispatchQueue(label: "com.myapp.parentQueue", qos: .userInitiated)
let childQueue = DispatchQueue(label: "com.myapp.childQueue", target: parentQueue)

// Задачи в childQueue будут иметь QoS от parentQueue
childQueue.async {
    performTask()
}

Установка приоритета отдельных задач

Можно переопределить QoS для конкретных задач независимо от очереди:

let queue = DispatchQueue.global(qos: .utility)

// Выполнение с повышенным приоритетом
queue.async(qos: .userInitiated) {
    performHighPriorityTask()
}

Адаптация под нагрузку системы с помощью DispatchSourceTimer

GCD умеет адаптироваться к системной нагрузке для оптимального расхода энергии:

let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
timer.schedule(deadline: .now(), repeating: .seconds(1), leeway: .milliseconds(100))
timer.setEventHandler {
    // Выполняется примерно каждую секунду с допустимым отклонением 100мс
    performPeriodicTask()
}
timer.resume()

Не забудьте загрузить приложение iJun в AppStore и подписаться на мой YouTube канал для получения дополнительных материалов по iOS-разработке.

Список литературы и дополнительных материалов

  1. Concurrency Programming Guide - Apple Developer Documentation
  2. DispatchQueue - Apple Developer Documentation
  3. Quality of Service - Apple Developer Documentation
Также читайте:  Группы диспетчеризации (Dispatch Groups) в GCD