1.1.1. Планировщик потоков ОС (Thread Scheduler)
Когда в системе одновременно выполняется множество потоков ОС (Software
Threads), а доступных логических ядер (Hardware Threads) ограниченное
количество, возникает вопрос: в каком порядке и на какое время назначать потоки на выполнение? Ответственность за это лежит на Планировщике потоков ОС (Thread Scheduler).
Ключевые характеристики планировщика:
- Единственный и Центральный: В рамках одной операционной системы работает единственный главный планировщик потоков.
- Вытесняющий (Preemptive): Планировщик работает в вытесняющем режиме. Это означает, что он сам
решает, когда прервать выполнение текущего потока и передать управление
другому. Он не "спрашивает" разрешения у потоков и не ждет, пока поток
добровольно отдаст управление (как в кооперативной многозадачности).
Планировщик жестко управляет процессорным временем.
Основные инструменты планировщика:
- Квант времени (Quantum Time):
Это фиксированный промежуток времени, выделяемый потоку для непрерывного выполнения на ядре.
Типичная длительность: Около 15 миллисекунд.
Назначение квантов: Планировщик выделяет потокам несколько таких квантов подряд.
На десктопных системах (Windows): Обычно 2 кванта (~30 мс).
На серверных системах (Windows Server): Обычно 12 квантов (~180 мс). Это уменьшает частоту переключений контекста при долгих вычислениях.
По истечении выделенных квантов планировщик может снять поток с выполнения, даже если он не завершил работу. - Приоритеты потоков: Планировщик учитывает приоритет
потока при выборе кандидата на выполнение. Чем выше приоритет, тем
быстрее и чаще поток будет получать процессорное время. (Подробнее о
приоритетах ниже).
Как это работает?
Планировщик поддерживает очереди потоков, готовых к выполнению (в состоянии Ready), для каждого логического ядра (или группы ядер). Основываясь на приоритете потока и истории выполнения, он выбирает следующий поток из очереди, выделяет ему квант(ы) времени
и переключает контекст. По истечении кванта или при возникновении
блокирующей операции (например, I/O), планировщик снимает поток с ядра и
выбирает следующий.
1.1.2. Приоритеты потоков
Приоритет потока — главный фактор, влияющий на то, как часто и как долго он будет получать процессорное время. Приоритет определяется двумя уровнями:
- Класс приоритета процесса (Process Priority Class):
Задает базовый уровень приоритета для всех потоков внутри процесса.
Основные классы (Windows): Idle, Below Normal, Normal (по умолчанию), Above Normal, High, Real-time.
Выбор класса процесса влияет на доступный диапазон приоритетов его потоков. - Относительный приоритет потока (Thread Priority Level):
Задает приоритет потока относительно базового приоритета его процесса.
Уровни: Idle, Lowest, Below Normal, Normal (по умолчанию), Above Normal, Highest, Time Critical.
Фактический приоритет: Комбинация класса процесса и уровня потока преобразуется в числовое значение от 0 до 31 (где 31 — наивысший).
Значение 0 зарезервировано системой.
Стандартные потоки приложения обычно работают в диапазоне 1-15.
Приоритеты Real-time (16-31) требуют крайней осторожности!
Важно! Потоки с приоритетом Real-time:
- Потоки в процессе с классом Real-time или с уровнем Time Critical получают приоритет выше потоков из любых процессов с нормальным или высоким приоритетом.
- Опасность: Если такой поток войдет в бесконечный цикл без блокировок (чистый CPU-bound), он может полностью заблокировать выполнение всех потоков с более низким приоритетом, включая критически важные системные процессы! Используйте Real-time только для задач, где это абсолютно необходимо (драйверы реального времени, специализированные системы управления).
Динамическое изменение приоритетов (Priority Boosting):
Планировщик может временно повышать приоритет потока для решения конкретных проблем:
- Предотвращение "Голодания" (Starvation): Если поток с низким приоритетом долго не может получить CPU, планировщик может немного повысить его приоритет.
- Освобождение Блокировок (Lock Convoys): Если поток долго удерживает блокировку (lock), из-за чего многие другие потоки ждут, планировщик может повысить приоритет владельца блокировки, чтобы он быстрее освободил ресурс.
- Завершение I/O операций: После завершения I/O операции, поток, ожидавший её, часто получает временное повышение приоритета, чтобы быстрее обработать полученные данные.
- Отзывчивость UI:
Потокам, обрабатывающим пользовательский ввод (например, в
UI-приложении), часто временно повышают приоритет для обеспечения
плавного интерфейса.
Ключевой вывод: Управление приоритетами — мощный, но опасный
инструмент. Необоснованное завышение приоритетов может
дестабилизировать всю систему. В .NET приложениях предпочтительнее
использовать ThreadPool и высокоуровневые примитивы (Task, async/await), а не ручное управление приоритетами потоков.
1.1.3. Состояния потока ОС (Thread States)
Поток ОС в процессе своего жизненного цикла проходит через несколько состояний (states). Планировщик использует эти состояния для управления выполнением. Основные состояния:
- Ready (Готов):
Поток готов к выполнению, все необходимые ресурсы доступны.
Он находится в очереди планировщика, ожидая, когда ему будет выделено время CPU на свободном логическом ядре.
Это состояние большинства работающих потоков большую часть времени. - Running (Выполняется):
Поток активно выполняется на логическом ядре процессора.
В этом состоянии он обрабатывает свои инструкции (CPU-bound или управляя I/O операциями).
В один момент времени на одном логическом ядре может выполняться только один поток. - Waiting (Ожидание):
Поток приостановил выполнение и ждет наступления некоторого события, чтобы продолжить.
Основные причины перехода в Waiting:
I/O Operations: Ожидание чтения/записи с диска, сети, ввода с клавиатуры и т.д.
Синхронизация: Ожидание освобождения блокировки (lock, Monitor, Mutex), сигнала от другого потока (EventWaitHandle, Semaphore), завершения другого потока (Thread.Join).
Таймеры: Ожидание истечения интервала времени (Thread.Sleep, Timer).
Пока поток находится в Waiting, планировщик не выделяет ему время CPU. При наступлении ожидаемого события поток переходит обратно в состояние Ready.
Другие состояния (менее частые, но важные):
- Initialized (Инициализирован): Поток создан системой, но ещё не готов к выполнению (инициализируются его структуры).
- Standby (Готов к запуску): Поток выбран планировщиком как следующий для выполнения на конкретном ядре. Ожидает фактического начала выполнения (переключения контекста).
- Terminated (Завершен): Поток завершил выполнение своего кода. Его ресурсы будут освобождены системой.
Цикл состояний типичного рабочего потока:
Ready -> (выбран планировщиком) -> Running -> (завершил квант / начал I/O / запросил блокировку) -> Waiting -> (событие произошло) -> Ready -> ... -> Terminated
Понимание состояний критически важно для диагностики проблем производительности (например, потоков, "зависших" в Waiting) и проектирования эффективных многопоточных приложений.