Планирование в пользовательском режиме

Предупреждение

С Windows 11 года планирование в пользовательском режиме не поддерживается. Все вызовы завершаются ошибкой ERROR_NOT_SUPPORTED.

Планирование в пользовательском режиме (UMS) — это упрощенный механизм, который приложения могут использовать для планирования собственных потоков. Приложение может переключаться между потоками UMS в пользовательском режиме без использования системного планировщика и восстановить контроль над процессором, если поток UMS блокируется в ядре. Потоки UMS отличаются от волокон тем, что каждый поток UMS имеет собственный контекст потока вместо совместного использования контекста потока одного потока. Возможность переключения между потоками в пользовательском режиме делает UMS более эффективным, чем пулы потоков для управления большим количеством краткосрочных рабочих элементов, требующих нескольких системных вызовов.

UMS рекомендуется для приложений с высокими требованиями к производительности, которые должны эффективно выполнять множество потоков одновременно в многопроцессорных или многоядерных системах. Чтобы воспользоваться преимуществами UMS, приложение должно реализовать компонент планировщика, который управляет потоками UMS приложения и определяет, когда они должны выполняться. Разработчикам следует рассмотреть вопрос о том, оправдывают ли требования к производительности приложения работу, связанную с разработкой такого компонента. Приложения с умеренными требованиями к производительности могут быть более удовлетворены, разрешив системному планировщику планировать свои потоки.

UMS доступна для 64-разрядных приложений, работающих в версиях AMD64 и Itanium Windows 7 и Windows Server 2008 R2 Windows 10 версии 21H2 и Windows Server 2022. Эта функция недоступна в Arm64, 32-разрядных версиях Windows или Windows 11.

Дополнительные сведения см. в следующих разделах:

Планировщик UMS

Планировщик UMS приложения отвечает за создание, администрирование и удаление потоков UMS, а также за определение потока UMS для запуска. Планировщик приложения выполняет следующие задачи:

  • Создает один поток планировщика UMS для каждого процессора, на котором приложение будет запускать рабочие потоки UMS.
  • Создает рабочие потоки UMS для выполнения работы приложения.
  • Поддерживает собственную очередь готовых потоков рабочих потоков, готовых к запуску, и выбирает потоки для запуска на основе политик планирования приложения.
  • Создает и отслеживает один или несколько списков завершения, в которых система помещает потоки в очередь после завершения обработки в ядре. К ним относятся только что созданные рабочие потоки и потоки, ранее заблокированные в системном вызове, которые становятся разблокированными.
  • Предоставляет функцию точки входа планировщика для обработки уведомлений из системы. Система вызывает функцию точки входа, когда создается поток планировщика, рабочий поток блокирует системный вызов или рабочий поток явно возвращает управление.
  • Выполняет задачи очистки для рабочих потоков, которые завершили выполнение.
  • Выполняет упорядоченное завершение работы планировщика по запросу приложения.

Поток планировщика UMS

Поток планировщика UMS — это обычный поток, который преобразовался в UMS путем вызова функции EnterUmsSchedulingMode . Системный планировщик определяет время выполнения потока планировщика UMS на основе его приоритета относительно других готовых потоков. На процессор, на котором выполняется поток планировщика, влияет сходство потока, как и для потоков, не относящихся к UMS.

Вызывающий объект EnterUmsSchedulingMode указывает список завершения и функцию точки входа UmsSchedulerProc , связанную с потоком планировщика UMS. Система вызывает указанную функцию точки входа после завершения преобразования вызывающего потока в UMS. Функция точки входа планировщика отвечает за определение соответствующего следующего действия для указанного потока. Дополнительные сведения см. в разделе Функция точки входа планировщика UMS далее в этом разделе.

Приложение может создать один поток планировщика UMS для каждого процессора, который будет использоваться для запуска потоков UMS. Приложение также может задать сходство каждого потока планировщика UMS для конкретного логического процессора, который, как правило, исключает несвязанные потоки из запуска на этом процессоре, фактически резервируя его для этого потока планировщика. Имейте в виду, что настройка сходства потоков таким образом может повлиять на общую производительность системы, так как другие процессы, которые могут выполняться в системе. Дополнительные сведения о сходстве потоков см. в разделе Несколько процессоров.

Рабочие потоки UMS, контексты потоков и списки завершения

Рабочий поток UMS создается путем вызова CreateRemoteThreadEx с атрибутом PROC_THREAD_ATTRIBUTE_UMS_THREAD и указания контекста потока UMS и списка завершения.

Контекст потока UMS представляет состояние потока UMS рабочего потока и используется для идентификации рабочего потока в вызовах функций UMS. Он создается путем вызова Метода CreateUmsThreadContext.

Список завершения создается путем вызова функции CreateUmsCompletionList . Список завершения получает рабочие потоки UMS, которые завершили выполнение в ядре и готовы к запуску в пользовательском режиме. Только система может помещать рабочие потоки в очередь в список завершения. Новые рабочие потоки UMS автоматически помещаются в очередь в список завершения, указанный при создании потоков. Ранее заблокированные рабочие потоки также помещаются в очередь в список завершения, если они больше не блокируются.

Каждый поток планировщика UMS связан с одним списком завершения. Однако один и тот же список завершения может быть связан с любым количеством потоков планировщика UMS, а поток планировщика может извлекать контексты UMS из любого списка завершения, для которого у него есть указатель.

Каждый список завершения имеет связанное событие, которое сигнализируется системой при постановке в очередь одного или нескольких рабочих потоков в пустой список. Функция GetUmsCompletionListEvent извлекает дескриптор события для указанного списка завершения. Приложение может ожидать более одного события списка завершения вместе с другими событиями, которые для приложения имеет смысл.

Функция точки входа планировщика UMS

Функция точки входа планировщика приложения реализована как функция UmsSchedulerProc . Система вызывает функцию точки входа планировщика приложения в следующее время:

  • При преобразовании потока, отличного от UMS, в поток планировщика UMS путем вызова метода EnterUmsSchedulingMode.
  • Когда рабочий поток UMS вызывает UmsThreadYield.
  • Когда рабочий поток UMS блокируется в системной службе, например системный вызов или ошибка страницы.

Параметр Reason функции UmsSchedulerProc указывает причину вызова функции точки входа. Если функция точки входа была вызвана из-за создания нового потока планировщика UMS, параметр SchedulerParam содержит данные, указанные вызывающим объектом Объекта EnterUmsSchedulingMode. Если функция точки входа была вызвана из-за получения рабочего потока UMS, параметр SchedulerParam содержит данные, указанные вызывающим объектом UmsThreadYield. Если функция точки входа была вызвана из-за блокировки рабочего потока UMS в ядре, параметр SchedulerParam имеет значение NULL.

Функция точки входа планировщика отвечает за определение соответствующего следующего действия для указанного потока. Например, если рабочий поток заблокирован, функция точки входа планировщика может запустить следующий доступный готовый рабочий поток UMS.

При вызове функции точки входа планировщика планировщик должен попытаться получить все элементы в связанном списке завершения, вызвав функцию DequeueUmsCompletionListItems . Эта функция извлекает список контекстов потоков UMS, которые завершили обработку в ядре и готовы к запуску в пользовательском режиме. Планировщик приложения не должен запускать потоки UMS непосредственно из этого списка, так как это может привести к непредсказуемому поведению в приложении. Вместо этого планировщик должен извлечь все контексты потоков UMS, вызвав функцию GetNextUmsListItem один раз для каждого контекста, вставить контексты потоков UMS в очередь готовых потоков планировщика и только затем запускать потоки UMS из очереди готовых потоков.

Если планировщику не нужно ждать нескольких событий, он должен вызвать DequeueUmsCompletionListItems с ненулевым параметром времени ожидания, чтобы функция ждала события списка завершения перед возвратом. Если планировщику нужно ждать нескольких событий списка завершения, он должен вызвать DequeueUmsCompletionListItems с параметром времени ожидания, равным нулю, чтобы функция возвращала немедленно, даже если список завершения пуст. В этом случае планировщик может явно ожидать событий списка завершения, например с помощью WaitForMultipleObjects.

Выполнение потока UMS

Созданный рабочий поток UMS помещается в очередь в указанный список завершения и не запускается до тех пор, пока планировщик UMS приложения не выберет его для запуска. Это отличается от потоков, отличных от UMS, которые системный планировщик автоматически планирует запускать, если вызывающий объект явно не создаст приостановленный поток.

Планировщик запускает рабочий поток, вызывая ExecuteUmsThread с контекстом UMS рабочего потока. Рабочий поток UMS выполняется до тех пор, пока не завершит работу путем вызова функции UmsThreadYield , блокирует или завершает работу.

Рекомендации по UMS

Приложения, реализующие UMS, должны следовать следующим рекомендациям:

  • Базовые структуры для контекстов потоков UMS управляются системой и не должны изменяться напрямую. Вместо этого используйте QueryUmsThreadInformation и SetUmsThreadInformation , чтобы получить и задать сведения о рабочем потоке UMS.
  • Чтобы предотвратить взаимоблокировку, поток планировщика UMS не должен совместно использовать блокировки с рабочими потоками UMS. Сюда входят как блокировки, созданные приложением, так и системные блокировки, которые получаются косвенно в результате таких операций, как выделение из кучи или загрузка библиотек DLL. Например, предположим, что планировщик запускает рабочий поток UMS, который загружает библиотеку DLL. Рабочий поток получает блокировку и блоки загрузчика. Система вызывает функцию точки входа планировщика, которая затем загружает библиотеку DLL. Это приводит к взаимоблокировке, так как блокировка загрузчика уже удерживается и не может быть освобождена до тех пор, пока не разблокируется первый поток. Чтобы избежать этой проблемы, делегируйте работу, которая может совместно использовать блокировки с рабочими потоками UMS, выделенному рабочему потоку UMS или потоку, отличному от UMS.
  • UMS является наиболее эффективным, когда большая часть обработки выполняется в пользовательском режиме. По возможности избегайте системных вызовов в рабочих потоках UMS.
  • Рабочие потоки UMS не должны предполагать, что используется системный планировщик. Это предположение может иметь незначительные последствия; Например, если поток в неизвестном коде задает приоритет или сходство потока, планировщик UMS может по-прежнему переопределить его. Код, предполагающий, что используется системный планировщик, может не работать должным образом и может нарушить работу при вызове потока UMS.
  • Системе может потребоваться заблокировать контекст потока рабочего потока UMS. Например, вызов асинхронной процедуры в режиме ядра (APC) может изменить контекст потока UMS, поэтому контекст потока должен быть заблокирован. Если планировщик пытается выполнить контекст потока UMS, пока он заблокирован, вызов завершится ошибкой. Это поведение по умолчанию, и планировщик должен быть разработан для повторного доступа к контексту потока UMS.