Найти в Дзене

Планировщик Thread Scheduler

1.1.1. Планировщик потоков ОС (Thread Scheduler) Когда в системе одновременно выполняется множество потоков ОС (Software
Threads), а доступных логических ядер (Hardware Threads) ограниченное
количество, возникает вопрос: в каком порядке и на какое время назначать потоки на выполнение? Ответственность за это лежит на Планировщике потоков ОС (Thread Scheduler). Ключевые характеристики планировщика: Основные инструменты планировщика: Как это работает?
Планировщик поддерживает очереди потоков, готовых к выполнению (в состоянии Ready), для каждого логического ядра (или группы ядер). Основываясь на приоритете потока и истории выполнения, он выбирает следующий поток из очереди, выделяет ему квант(ы) времени
и переключает контекст. По истечении кванта или при возникновении
блокирующей операции (например, I/O), планировщик снимает поток с ядра и
выбирает следующий. 1.1.2. Приоритеты потоков Приоритет потока — главный фактор, влияющий на то, как часто и как долго он будет получать процессо

1.1.1. Планировщик потоков ОС (Thread Scheduler)

Когда в системе одновременно выполняется множество потоков ОС (Software
Threads), а доступных логических ядер (Hardware Threads) ограниченное
количество, возникает вопрос:
в каком порядке и на какое время назначать потоки на выполнение? Ответственность за это лежит на Планировщике потоков ОС (Thread Scheduler).

Ключевые характеристики планировщика:

  1. Единственный и Центральный: В рамках одной операционной системы работает единственный главный планировщик потоков.
  2. Вытесняющий (Preemptive): Планировщик работает в вытесняющем режиме. Это означает, что он сам
    решает, когда прервать выполнение текущего потока и передать управление
    другому. Он не "спрашивает" разрешения у потоков и не ждет, пока поток
    добровольно отдаст управление (как в кооперативной многозадачности).
    Планировщик жестко управляет процессорным временем.

Основные инструменты планировщика:

  1. Квант времени (Quantum Time):
    Это фиксированный промежуток времени, выделяемый потоку для непрерывного выполнения на ядре.
    Типичная длительность: Около 15 миллисекунд.
    Назначение квантов: Планировщик выделяет потокам несколько таких квантов подряд.
    На
    десктопных системах (Windows): Обычно 2 кванта (~30 мс).
    На
    серверных системах (Windows Server): Обычно 12 квантов (~180 мс). Это уменьшает частоту переключений контекста при долгих вычислениях.
    По истечении выделенных квантов планировщик может снять поток с выполнения, даже если он не завершил работу.
  2. Приоритеты потоков: Планировщик учитывает приоритет
    потока при выборе кандидата на выполнение. Чем выше приоритет, тем
    быстрее и чаще поток будет получать процессорное время. (Подробнее о
    приоритетах ниже).

Как это работает?
Планировщик поддерживает
очереди потоков, готовых к выполнению (в состоянии Ready), для каждого логического ядра (или группы ядер). Основываясь на приоритете потока и истории выполнения, он выбирает следующий поток из очереди, выделяет ему квант(ы) времени
и переключает контекст. По истечении кванта или при возникновении
блокирующей операции (например, I/O), планировщик снимает поток с ядра и
выбирает следующий.

1.1.2. Приоритеты потоков

Приоритет потока — главный фактор, влияющий на то, как часто и как долго он будет получать процессорное время. Приоритет определяется двумя уровнями:

  1. Класс приоритета процесса (Process Priority Class):
    Задает базовый уровень приоритета для всех потоков внутри процесса.
    Основные классы (Windows): Idle, Below Normal, Normal (по умолчанию), Above Normal, High, Real-time.
    Выбор класса процесса влияет на доступный диапазон приоритетов его потоков.
  2. Относительный приоритет потока (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):
Планировщик может
временно повышать приоритет потока для решения конкретных проблем:

  1. Предотвращение "Голодания" (Starvation): Если поток с низким приоритетом долго не может получить CPU, планировщик может немного повысить его приоритет.
  2. Освобождение Блокировок (Lock Convoys): Если поток долго удерживает блокировку (lock), из-за чего многие другие потоки ждут, планировщик может повысить приоритет владельца блокировки, чтобы он быстрее освободил ресурс.
  3. Завершение I/O операций: После завершения I/O операции, поток, ожидавший её, часто получает временное повышение приоритета, чтобы быстрее обработать полученные данные.
  4. Отзывчивость UI:
    Потокам, обрабатывающим пользовательский ввод (например, в
    UI-приложении), часто временно повышают приоритет для обеспечения
    плавного интерфейса.

Ключевой вывод: Управление приоритетами — мощный, но опасный
инструмент. Необоснованное завышение приоритетов может
дестабилизировать всю систему. В .NET приложениях предпочтительнее
использовать ThreadPool и высокоуровневые примитивы (Task, async/await), а не ручное управление приоритетами потоков.

1.1.3. Состояния потока ОС (Thread States)

Поток ОС в процессе своего жизненного цикла проходит через несколько состояний (states). Планировщик использует эти состояния для управления выполнением. Основные состояния:

  1. Ready (Готов):
    Поток готов к выполнению, все необходимые ресурсы доступны.
    Он находится в
    очереди планировщика, ожидая, когда ему будет выделено время CPU на свободном логическом ядре.
    Это состояние большинства работающих потоков большую часть времени.
  2. Running (Выполняется):
    Поток активно выполняется на логическом ядре процессора.
    В этом состоянии он обрабатывает свои инструкции (CPU-bound или управляя I/O операциями).
    В один момент времени на одном логическом ядре может выполняться
    только один поток.
  3. 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) и проектирования эффективных многопоточных приложений.