Что такое инверсия приоритетов, в чем проблема и как она проявляется
Что проверяют
Понимаете ли вы, что приоритет задачи не соблюдается сам по себе, стоит появиться общей блокировке. Вопрос идёт следом за разговором про классы качества обслуживания и проверяет, сможете ли вы объяснить, почему интерфейс подтормаживает, хотя тяжёлую работу вынесли в фон.
Как отвечать
Сначала схема. Низкоприоритетная задача захватила ресурс. Высокоприоритетная просит тот же ресурс и встаёт в ожидание: пока владелец не отпустит, она не сдвинется, и всё это время её фактический приоритет равен приоритету владельца. Это ограниченная инверсия: неприятно, но предсказуемо.
Опасный вариант появляется, когда есть третья задача, среднего приоритета, которой ресурс не нужен. Планировщик предпочитает её владельцу, тот не получает процессорного времени и не отпускает ресурс, а ожидание растягивается на неопределённое время. Это и называют неограниченной инверсией.
Лечение по сути одно: система временно поднимает приоритет владельца до приоритета ожидающего. В экосистеме iOS это происходит там, где ядру известен владелец: при синхронной отправке работы на очередь с более низким классом обслуживания и при блокировке, которая владельца хранит. Семафор и условная переменная владельца не знают, донора приоритета у них нет, и ожидание на семафоре из потока с высоким классом обслуживания это классическая ловушка.
Про диагностику: проверка потоков в Xcode сообщает об инверсии во время выполнения.
Где спотыкаются
Называют инверсией обычную взаимоблокировку. При ней не продвигается никто и никогда, а при инверсии система работает, просто важная задача ждёт дольше, чем ей полагается.
Забывают про третьего участника и потому не могут объяснить, почему проблему называют неограниченной. Без задачи среднего приоритета ожидание ограничено временем удержания ресурса, вся беда именно в ней.
Лечат симптом, повышая класс обслуживания всем подряд вплоть до самого высокого. Когда он высокий у всех, он не значит ничего: потоков больше, расход батареи выше, а общая блокировка на месте. Правильный путь это убрать разделяемый ресурс (актор, своя последовательная очередь, неизменяемые данные) либо удерживать его коротко.