Наследование приоритетов
Наследование приоритетов
Одним из интересных моментов в операционных системах реального времени является феномен инверсии приоритетов.
Инверсия приоритетов наблюдается, например, в случае, когда поток с низким приоритетом потребляет все процессорное время, в то время как в состоянии готовности находится поток с более высоким приоритетом.
Вы, наверное, сейчас думаете: «Минуточку! Вы утверждали ранее, что поток с более высоким приоритетом будет всегда вытеснять поток с более низким приоритетом! Как же такое может быть?»
Вы абсолютно правы. Поток с более высоким приоритетом всегда будет вытеснять поток с более низким приоритетом. Но при этом все-таки может произойти кое-что интересное. Давайте рассмотрим сценарий с тремя потоками (в трех различных процессах, для простоты рассмотрения), где «L» — поток с низким приоритетом, «Н» — поток с высоким приоритетом, и «S» — сервер. На рисунке показаны все три потока со своими приоритетами.
Три потока с различными приоритетами.
В данный момент выполняется поток Н. Потоку сервера S, имеющему наивысший приоритет, пока делать нечего, так что он находится в режиме ожидания и блокирован на функции MsgReceive(). Поток L и хотел бы работать, но его приоритет ниже, чем у потока Н, который выполняется в данный момент. Все как вы и предполагали, да?
А теперь представьте себе, что поток Н принял решение «прикорнуть» на 100 миллисекунд — возможно, чтобы подождать медленное оборудование. Теперь выполняется поток L.
Вот тут-то все интересное и начинается.
В пределах своего нормального функционирования поток L посылает сообщение потоку сервера S, принуждая этим сервер S перейти в состояние READY и (поскольку поток S имеет высший приоритет из всех готовых к выполнению потоков) начать выполняться. К великому сожалению, сообщение, которое поток L направил к потоку сервера S, было сформулировано так: «Вычислить значение Пи с точностью до 50 знаков после запятой».
Очевидно, это займет более чем 100 миллисекунд. Поэтому, когда 100 миллисекунд сна потока Н истекут, поток Н перейдет в состояние READY — угадайте, что дальше? Поток Н не активизируется, постольку в состоянии READY находится поток S, имеющий более высокий приоритет!
Что здесь произошло? Произошло то, что поток с низким приоритетом «отстранил» от работы поток с более высоким приоритетом путем передачи процессора потоку с еще более высоким приоритетом. Это явление называется инверсией приоритетов.
Чтобы научиться не допускать таких вещей, мы должны поговорить о наследовании приоритетов. Простой вариант реализации наследования приоритета — заставить сервер S унаследовать приоритет клиентского потока:
Блокированные потоки.
При таком сценарии по истечении 100-миллисекундного интервала бездействия потока Н этот поток переходит в состояние READY и немедленно ставится на выполнение как имеющий наивысший приоритет.
Неплохо; однако, здесь есть еще один тонкий момент.
Предположим, что потоку Н вдруг становится нужно выполнить какие-то вычисления — например, найти 5034-е по порядку простое число. Он посылает сообщение потоку сервера S и блокируется.
Однако, в данный момент S по-прежнему вычисляет значение Пи, находясь на приоритете 5! В нашей выбранной для примера системе наверняка достаточно других потоков, имеющих приоритет выше, чем 5, которым тоже нужен процессор. Это автоматически значит, что процессорного времени на вычисление значения Пи у S остается не так уж и много.
Это еще одна форма инверсии приоритетов. В этом случае поток с низким приоритетом помешал потоку с более высоким приоритетом получить доступ к ресурсу. Сравните это с первой формой инверсии приоритета, где поток с низким приоритетом реально потреблял ресурсы процессора — в рассматриваемом сейчас случае этот поток не дает более приоритетному потоку доступа к ресурсам процессора, но сам при этом непосредственно их не потребляет.
К счастью, данная проблема решается тоже достаточно просто. Достаточно увеличить приоритет сервера так, чтобы он был равен наивысшему из приоритетов всех заблокированных клиентов:
Повышение приоритета сервера.
Здесь мы немного «обделяем» другие потоки, позволяя заданию потока L выполняться с приоритетом выше, чем он сам, но зато гарантируем, что поток Н получит свою заслуженную порцию процессорного времени.