Какие основные проблемы возникают при работе с многопоточностью?

«Какие основные проблемы возникают при работе с многопоточностью?» — вопрос из категории Многопоточность, который задают на 47% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Работа с несколькими потоками сопряжена с рядом классических проблем:

Проблема Описание Пример риска в Swift/iOS
Состояние гонки (Race Condition) Неопределённый результат из-за неконтролируемого порядка доступа потоков к общим данным. Неатомарное изменение общего счётчика из нескольких потоков приводит к потере обновлений.
Взаимная блокировка (Deadlock) Два или более потока бесконечно ожидают освобождения ресурсов, заблокированных друг другом. Синхронный вызов (sync) на текущей serial очереди или циклическая блокировка мьютексов.
Инверсия приоритетов (Priority Inversion) Низкоприоритетный поток удерживает ресурс (например, блокировку), нужный высокоприоритетному, блокируя его выполнение. Может происходить при использовании общих DispatchQueue или NSLock с разными QoS.
Голодание (Starvation) Поток не может получить доступ к необходимым ресурсам и не прогрессирует. Долгие задачи на serial очереди или неправильная настройка приоритетов в OperationQueue.
Проблемы с памятью Утечки, висячие указатели из-за неправильного управления жизненным циклом объектов между потоками. Сильные ссылки в захватах замыканий, удерживающие объекты в фоновых потоках.

Конкретные примеры и решения:

  • Гонка данных:
    var unsafeCounter = 0
    DispatchQueue.concurrentPerform(iterations: 1000) { _ in
        unsafeCounter += 1 // НЕБЕЗОПАСНО! Итоговое значение будет меньше 1000.
    }
    // Решение: Использовать изолированный актор или очередь с барьером.
  • Обновление UI не из главного потока:
    DispatchQueue.global().async {
        self.label.text = "New Text" // КРАШ или неопределённое поведение.
    }
    // Решение: Всегда обновлять UI через `DispatchQueue.main` или `MainActor`.

Основные инструменты предотвращения: Акторы (actor), семафоры (DispatchSemaphore), очереди с барьерами (DispatchQueue.barrier), атомарные операции, а также архитектурные паттерны, минимизирующие общее состояние.