← Назад к вопросам

Что такое голодание потока?

2.3 Middle🔥 112 комментариев
#Многопоточность и асинхронность

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое голодание потока (Thread Starvation)?

Голодание потока — это ситуация в многопоточном программировании, когда один или несколько потоков не могут получить доступ к необходимым ресурсам (чаще всего — процессорному времени или разделяемым данным) в течение длительного периода, в то время как другие потоки продолжают нормально выполняться. Это происходит из-за несправедливого или неоптимального планирования работы потоков со стороны системы, менеджера потоков (thread scheduler) или из-за ошибок в синхронизации (например, неправильного использования мьютексов (mutex), семафоров (semaphore), NSLock или @synchronized в Objective-C/Swift).

Основные причины в iOS/macOS разработке

1. Низкий приоритет потока (Thread Priority)

Потоки с низким приоритетом (QoS: .background) могут "голодать", если высокоприоритетные потоки (QoS: .userInteractive) постоянно заняты. GCD (Grand Central Dispatch) и система могут отдавать предпочтение потокам с высоким QoS.

// Поток с низким приоритетом может "голодать"
DispatchQueue.global(qos: .background).async {
    // Длительная задача
}

2. Некорректная синхронизация с блокировками (Locking)

  • Неосвобождаемый мьютекс: Если поток захватывает мьютекс и не освобождает его (например, из-за ошибки или deadlock).
let lock = NSLock()
lock.lock()
// Если здесь произойдет исключение или return, lock не освободится
// lock.unlock() // Пропущенный вызов
  • Инверсия приоритетов (Priority Inversion): Низкоприоритетный поток удерживает блокировку, нужную высокоприоритетному потоку, что блокирует оба (классический пример — Mars Pathfinder).

3. Некорректная работа с очередями (Dispatch Queues)

  • Блокирование главного потока (Main Queue): Долгие синхронные операции на главном потоке блокируют обновление UI.
// НЕПРАВИЛЬНО: блокирует главный поток
DispatchQueue.main.sync {
    // Тяжелая задача
}
  • Взаимные блокировки (Deadlock): Например, синхронный вызов (sync) на текущей очереди.
DispatchQueue.main.async {
    DispatchQueue.main.sync { // Deadlock!
        // Код никогда не выполнится
    }
}

4. Активное ожидание (Busy Waiting)

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

// ПЛОХОЙ ПРИМЕР: поток "жрет" CPU
while !resourceAvailable {
    // Пустая проверка
}

5. Несправедливые алгоритмы планирования

Некоторые алгоритмы (например, FIFO без учета приоритетов) могут приводить к голоданию.

Как избежать голодания потоков?

Используйте приоритеты разумно

  • Не злоупотребляйте высокими приоритетами (userInteractive).
  • Используйте .default или .utility для фоновых задач.

Корректно работайте с блокировками

  • Всегда освобождайте блокировки (используйте defer в Swift).
lock.lock()
defer { lock.unlock() }
// Критическая секция
  • Минимизируйте время удержания блокировки.
  • Используйте атомарные операции (atomic operations) или очереди (DispatchQueue) с барьерами (barrier) вместо явных блокировок.
// Потокобезопасный доступ через приватную очередь
private let concurrentQueue = DispatchQueue(label: "com.example.queue", 
                                           attributes: .concurrent)
private var _data = [String]()

var data: [String] {
    concurrentQueue.sync { _data }
}

func updateData(_ newItem: String) {
    concurrentQueue.async(flags: .barrier) {
        self._data.append(newItem)
    }
}

Избегайте активного ожидания

  • Используйте условные переменные (Condition Variables), DispatchSemaphore или механизмы уведомлений.
let semaphore = DispatchSemaphore(value: 0)
// Поток A: ожидает сигнала
DispatchQueue.global().async {
    semaphore.wait() // Без активного ожидания
}
// Поток B: сигнализирует
semaphore.signal()

Проектируйте асинхронные API

  • Используйте completion handlers, Delegate, RxSwift/Combine или async/await (Swift 5.5+), чтобы избежать блокировок.
// async/await минимизирует блокировки
func fetchData() async throws -> Data {
    let url = URL(string: "https://api.example.com/data")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return data
}

Мониторинг и инструменты

  • Используйте Instruments (особенно Time Profiler, System Trace).
  • Следите за антивацией (CPU usage), временем отклика (latency) и deadlock-ами.

Последствия голодания

  • Деградация производительности: UI "замирает", анимации прерываются.
  • Повышенное энергопотребление (особенно при active waiting).
  • Race conditions: Голодающие потоки могут пропустить важные события.
  • Краш приложения: Watchdog в iOS может завершить приложение, если главный поток блокирован слишком долго.

Голодание потоков — серьезная проблема, требующая внимания на этапе проектирования многопоточности. В iOS/macOS предпочтительно использовать высокоуровневые абстракции (GCD, OperationQueue, async/await), которые минимизируют риски, но не исключают их полностью. Всегда тестируйте многопоточный код под нагрузкой и используйте инструменты профилирования.

Что такое голодание потока? | PrepBro