Что такое инверсия приоритетов (Priority Inversion) в многопоточности?

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

Ответ

Инверсия приоритетов (Priority Inversion) — это проблемная ситуация в планировании задач с реальным временем (RTOS) или приоритетными очередями, когда задача с высоким приоритетом вынуждена ожидать выполнения задачи с низким приоритетом, что нарушает ожидаемую логику планировщика.

Классический сценарий (на примере трех задач):

  1. Задача L (Low Priority) захватывает разделяемый ресурс (например, блокировку NSLock).
  2. Задача H (High Priority) запускается и также пытается захватить тот же ресурс. Поскольку он занят, H блокируется и ждет.
  3. В этот момент запускается Задача M (Medium Priority), которая не использует общий ресурс. Планировщик, видя, что H заблокирована, а M готова к выполнению, отдает CPU задаче M.
  4. Результат: Задача M (средний приоритет) выполняется, в то время как задача H (высший приоритет) простаивает, ожидая L (низший приоритет). Приоритеты инвертировались.

Пример кода, моделирующего ситуацию:

let sharedResourceLock = NSLock()
let highPriorityQueue = DispatchQueue(label: "high", qos: .userInteractive)
let mediumPriorityQueue = DispatchQueue(label: "medium", qos: .utility)
let lowPriorityQueue = DispatchQueue(label: "low", qos: .background)

// Задача L (низкий приоритет) захватывает ресурс
lowPriorityQueue.async {
    sharedResourceLock.lock()
    print("Low priority task L: Lock acquired")
    sleep(2) // Имитация долгой работы с ресурсом
    sharedResourceLock.unlock()
    print("Low priority task L: Lock released")
}

// Небольшая задержка, чтобы L успел захватить lock
Thread.sleep(forTimeInterval: 0.1)

// Задача H (высокий приоритет) блокируется на ресурсе
highPriorityQueue.async {
    print("High priority task H: Waiting for lock...")
    sharedResourceLock.lock() // БЛОКИРОВКА! Ждет L.
    print("High priority task H: Lock acquired")
    sharedResourceLock.unlock()
}

// Задача M (средний приоритет) может выполняться, пока H ждет
mediumPriorityQueue.async {
    for i in 1...3 {
        print("Medium priority task M: Working... ((i))")
        sleep(1)
    }
}

Решения проблемы:

  • Наследование приоритета (Priority Inheritance): Когда задача H блокируется на ресурсе, удерживаемом L, приоритет L временно повышается до приоритета H. Это позволяет L быстрее завершиться и освободить ресурс. Эта политика используется, например, в pthread_mutex с атрибутом PRIO_INHERIT.
  • Потолок приоритетов (Priority Ceiling): Ресурсу назначается заранее известный высокий "потолочный" приоритет. Любая задача, захватившая этот ресурс, временно получает этот высокий приоритет, предотвращая выполнение задач среднего приоритета.

В iOS/ Swift: Системные механизмы (например, os_unfair_lock) и GCD в определенной степени помогают mitigate эту проблему, но понимание инверсии критично при проектировании собственных сложных систем синхронизации.