Переключение
потоков
Планировщик
ОС поддерживает для каждого из базовых уровней приоритета функционирование очереди
выполняемых или готовых к выполнению потоков (ready threads queue). Когда
процессор становится доступным, то планировщик производит переключение контекстов.
Здесь можно выделить такие шаги:
- сохранение контекста
потока, завершающего выполнение; О перемещение этого потока в конец своей
очереди;
- поиск очереди с высшим
приоритетом, которая содержит потоки, готовые к выполнению;
- выбор первого потока
из этой очереди, загрузка его контекста и запуск на выполнение.
Примечание
Если в системе за каждым
процессором закреплен хотя бы один поток с приоритетом 31, то остальные потоки
с более низким приоритетом не смогут получить доступ к процессору и поэтому
не будут выполняться. Такая ситуация называется starvation.
Различают потоки,
не готовые к выполнению. Это:
- потоки, которые при
создании имели флаг CREATE_SUSPENDED;
- потоки, выполнение которых
было прервано вызовом функции SuspendThread или SwitchToThread;
- потоки, которые ожидают
ввода или синхронизирующего события.
Блокированные
таким образом потоки или подвешенные (suspended) потоки не получают кванта
времени независимо от величины их приоритета. Типичными причинами переключения
контекстов являются следующие:
- истек квант времени,
- в очереди с более высоким
приоритетом появился поток, готовый к выполнению,
- текущий поток вынужден
ждать.
В последнем
случае система не ждет завершения кванта времени и отнимает управление, как
только поток впадает в ожидание. Возможный вариант развития событий изображен
на рис. 12.6.
Кроме рассмотренного
базового уровня каждый поток обладает динамическим приоритетом. Под этим понятием
скрываются временные колебания уровня, которые вызваны планировщиком. Он намеренно
вызывает такие колебания, для того чтобы убедиться в управляемости и реактивности
потока, а также для того, чтобы дать шанс потокам с низким приоритетом. Система
никогда не подстегивает потоки, приоритет которых итак высок (от 16 до 31).
Когда пользователь работает с каким-то процессом, то он считается активным (foreground),
а остальные процессы — фоновыми (background). При ускорении потока
(priority boost) система действует следующим образом: когда процесс с нормальным
классом приоритета «выходит на сцену» (is brought to the foreground), он получает
ускорение.
Рис.
12.6. Вытеснение потока с более низким приоритетом
Примечание
Термин foreground обозначает
то качество процесса, которое характеризует — его с точки зрения связи с активным
окном Windows. Foreground window — это окно, которое в данный момент находится
в фокусе и, следовательно, расположено поверх остальных. Это состояние может
быть получено как программным способом (вызов функции SetFocus), так и аппаратно
(пользователь щелкнул окно).
Плакировщик
изменяет класс процесса, связанного с этим окном, так чтобы он был больше или
равен классу любого процесса, связанного с background-окном. Класс приоритета
вновь восстанавливается при потере процессом статуса foreground. Отметьте, что
в Windows NT/2000 пользователь может управлять величиной ускорения всех процессов
класса NORMAL_PRIORITY с помощью панели управления (команда System, вкладка
Performance, ползунок Boost Application Performance).
Когда окно
получает сообщение типа WM_TIMER, WM_LBUTTONDOWN или WM_KEYDOWN, планировщик
также ускоряет (boosts) ноток, владеющий этим окном. Существуют еще ситуации,
когда планировщик временно повышает уровень приоритета потока. Довольно часто
потоки ожидают возможности обратиться к диску. Когда диск освобождается, блокированный
поток просыпается и в этот момент система повышает его уровень приоритета. После
ускорения потока планировщик постепенно снижает уровень приоритета до базового
значения. Уровень снижается на одну единицу после завершения очередного кванта
времени. Иногда система инвертирует приоритеты, чтобы разрешить конфликты типа
deadlock. Благодаря динамике изменения приоритетов потоки активного процесса
вытесняют потоки фонового процесса, а потоки с низким приоритетом все-таки имеют
шанс получить управление.
Представьте
такую ситуацию: поток 1 с высоким приоритетом вынужден ждать, пока поток 2 с
низким приоритетом выполняет код в критической секции. В это же время готов
к выполнению поток 3 со средним значением приоритета. Он получает время процессора,
а два других потока застревают на неопределенное время, так как поток 2 не в
состоянии вытеснить поток 3, а поток 1 помнит, что надо ждать, когда поток 2
выйдет из критической секции. Отметьте, что планировщик не способен провести
анализ подобной ситуации и решить проблему. Он видит ситуацию только в свете
существования двух готовых (ready) потоков с разными приоритетами.
Windows NT/2000
разрешает эту ситуацию так. Планировщик увеличивает приоритеты готовых потоков
на величину, выбранную случайным образом. В нашем примере это приводит к тому,
что поток с низким приоритетом получает шанс на время процессора и, в течение,
может быть, нескольких квантов закончит выполнение кодов критической секции.
Как только это произойдет, поток 1 с высоким приоритетом сразу получит управление
и сможет, вытеснив поток 3, начать выполнение кодов критической секции.
Windows 95
разрешит эту ситуацию по-другому. Она определяет факт того, что поток 1 с высоким
приоритетом зависит от потока 2 с низким приоритетом, и повышает приоритет второго
потока до величины приоритета первого. Это приводит к тому, что поток 3 вытесняется
потоком 2 и он, закончив выполнение кодов критической секции, пропускает вперед
ждавший поток 1.
В системе Windows
NT/2000 программист имеет возможность управлять процессом ускорения потоков
с помощью API-функций SetProcessPriorityBoost (все потоки данного процесса)
или SetThreadPriorityBoost (данный поток) в пределах, которые обозначены на
рис. 12.7. Функции GetProcessPriorityBoost и GetThreadPriorityBoost позволяют
выяснить текущее состояние флага.
Рис. 12.7.
Диапазон изменения приоритета потока
При наличии
нескольких процессоров Windows NT применяет симметричную модель распределения
потоков по процессорам (symmetric multiprocessing SMP), Это означает, что любой
поток может быть направлен на любой процессор, но программист может ввести некоторые
коррективы в эту модель равноправного распределения. Функции SetProcessAffinityMask
и SetThreadAffinityMask позволяют указать предпочтения в смысле выбора процессора
для всех потоков процесса или для одного определенного потока. Потоковое предпочтение
(thread affinity) вынуждает систему выбирать процессоры только из множества,
указанного в маске. Существует также возможность для каждого потока указать
один предпочтительный процессор. Это делается с помощью функции SetThreadidealProcessor.
Это указание служит подсказкой для планировщика заданий, но не гарантирует строгого
соблюдения.