用户模式计划

警告

自Windows 11起,不支持用户模式计划。 所有调用都失败,并出现错误 ERROR_NOT_SUPPORTED

用户模式计划 (UMS) 是一种轻型机制,应用程序可以使用该机制来计划自己的线程。 应用程序可以在用户模式下的 UMS 线程之间切换,而无需涉及 系统计划程序 ,并在内核中阻止 UMS 线程时重新获得处理器的控制权。 UMS 线程与 光纤 的不同之处在于,每个 UMS 线程都有自己的线程上下文,而不是共享单个线程的线程上下文。 在用户模式下在线程之间切换的功能使 UMS 比 线程池 更高效,用于管理大量需要少量系统调用的短工期工作项。

对于需要高效同时在多处理器或多核系统上同时运行多个线程的高性能要求的应用程序,建议使用 UMS。 若要利用 UMS,应用程序必须实现一个计划程序组件,该组件管理应用程序的 UMS 线程并确定它们应何时运行。 开发人员应考虑其应用程序性能要求是否证明开发此类组件所涉及的工作是正当的。 允许系统计划程序安排其线程,从而更好地满足中等性能要求的应用程序。

UMS 适用于在 AMD64 和 Itanium 版本的 Windows 7 和 Windows Server 2008 R2 到 Windows 10 版本 21H2 和 Windows Server 2022 上运行的 64 位应用程序。 此功能在 Arm64、32 位版本的 Windows 或 Windows 11 上不可用。

有关详细信息,请参阅以下部分:

UMS 计划程序

应用程序的 UMS 计划程序负责创建、管理和删除 UMS 线程,并确定要运行的 UMS 线程。 应用程序的计划程序执行以下任务:

  • 为应用程序将运行 UMS 工作线程的每个处理器创建一个 UMS 计划程序线程。
  • 创建 UMS 工作线程以执行应用程序的工作。
  • 维护自己的已准备好运行的工作线程的就绪线程队列,并根据应用程序的计划策略选择要运行的线程。
  • 创建并监视一个或多个完成列表,其中系统在内核中完成线程处理后将线程排入队列。 其中包括新创建的工作线程,以及以前在取消阻止的系统调用上阻止的线程。
  • 提供计划程序入口点函数来处理来自系统的通知。 当创建计划程序线程、在系统调用上阻止工作线程或工作线程显式生成控制权时,系统会调用入口点函数。
  • 为已完成运行的工作线程执行清理任务。
  • 在应用程序请求时,对计划程序执行有序关闭。

UMS 计划程序线程

UMS 计划程序线程是通过调用 EnterUmsSchedulingMode 函数将自身转换为 UMS 的普通线程。 系统计划程序根据相对于其他就绪线程的优先级确定 UMS 计划程序线程的运行时间。 运行计划程序线程的处理器受线程相关性的影响,与非 UMS 线程相同。

EnterUmsSchedulingMode 的调用方指定要与 UMS 计划程序线程关联的完成列表和 UmsSchedulerProc 入口点函数。 完成将调用线程转换为 UMS 后,系统会调用指定的入口点函数。 计划程序入口点函数负责为指定线程确定适当的下一个操作。 有关详细信息,请参阅本主题后面的 UMS 计划程序入口点函数

应用程序可能会为每个将用于运行 UMS 线程的处理器创建一个 UMS 计划程序线程。 应用程序还可能为特定逻辑处理器设置每个 UMS 计划程序线程的相关性,这倾向于排除在该处理器上运行的不相关的线程,从而有效地将其保留给该计划程序线程。 请注意,以这种方式设置线程相关性可能会使系统上运行的其他进程耗尽,从而影响整体系统性能。 有关线程相关性的详细信息,请参阅 多处理器

UMS 工作线程、线程上下文和完成列表

通过使用 PROC_THREAD_ATTRIBUTE_UMS_THREAD 属性调用 CreateRemoteThreadEx 并指定 UMS 线程上下文和完成列表来创建 UMS 工作线程。

UMS 线程上下文表示工作线程的 UMS 线程状态,用于在 UMS 函数调用中标识工作线程。 它是通过调用 CreateUmsThreadContext 创建的。

通过调用 CreateUmsCompletionList 函数创建完成列表。 完成列表接收已在内核中完成执行并已准备好在用户模式下运行的 UMS 工作线程。 只有系统才能将工作线程排到完成列表。 新的 UMS 工作线程会自动排队到创建线程时指定的完成列表。 以前阻止的工作线程在不再被阻止时也会排队到完成列表。

每个 UMS 计划程序线程都与单个完成列表相关联。 但是,同一完成列表可以与任意数量的 UMS 计划程序线程相关联,计划程序线程可以从具有指针的任何完成列表中检索 UMS 上下文。

每个完成列表都有一个关联的事件,系统在将一个或多个工作线程排队到空列表时发出信号。 GetUmsCompletionListEvent 函数检索指定完成列表的事件句柄。 应用程序可以等待多个完成列表事件以及对应用程序有意义的其他事件。

UMS 计划程序入口点函数

应用程序的计划程序入口点函数作为 UmsSchedulerProc 函数实现。 系统在以下时间调用应用程序的计划程序入口点函数:

  • 通过调用 EnterUmsSchedulingMode 将非 UMS 线程转换为 UMS 计划程序线程时。
  • 当 UMS 工作线程调用 UmsThreadYield 时。
  • 当 UMS 工作线程阻止系统服务(如系统调用或页面错误)时。

UmsSchedulerProc 函数的 Reason 参数指定调用入口点函数的原因。 如果由于创建新的 UMS 计划程序线程而调用入口点函数, 则 SchedulerParam 参数包含 由 EnterUmsSchedulingMode 的调用方指定的数据。 如果由于生成 UMS 工作线程而调用入口点函数, 则 SchedulerParam 参数包含 UmsThreadYield 的调用方指定的数据。 如果由于内核中阻止了 UMS 工作线程而调用入口点函数, 则 SchedulerParam 参数为 NULL。

计划程序入口点函数负责为指定线程确定适当的下一个操作。 例如,如果阻止工作线程,计划程序入口点函数可能会运行下一个可用的就绪 UMS 工作线程。

调用计划程序入口点函数时,应用程序的计划程序应尝试通过调用 DequeueUmsCompletionListItems 函数来检索其关联的完成列表中的所有项。 此函数检索已在内核中完成处理并已准备好在用户模式下运行的 UMS 线程上下文的列表。 应用程序的计划程序不应直接从此列表中运行 UMS 线程,因为这可能会导致应用程序中出现不可预知的行为。 相反,计划程序应通过为每个上下文调用 一次 GetNextUmsListItem 函数来检索所有 UMS 线程上下文,将 UMS 线程上下文插入到计划的就绪线程队列中,然后才从就绪线程队列运行 UMS 线程。

如果计划程序不需要等待多个事件,则应使用非零超时参数调用 DequeueUmsCompletionListItems ,以便函数在返回之前等待完成列表事件。 如果计划程序确实需要等待多个完成列表事件,则应使用超时参数为零调用 DequeueUmsCompletionListItems ,以便即使完成列表为空,函数也会立即返回。 在这种情况下,计划程序可以显式等待完成列表事件,例如,通过使用 WaitForMultipleObjects

UMS 线程执行

新创建的 UMS 工作线程将排队到指定的完成列表,在应用程序的 UMS 计划程序选择要运行之前不会开始运行。 这不同于非 UMS 线程,系统计划程序会自动计划运行非 UMS 线程,除非调用方显式创建挂起的线程。

计划程序通过使用工作线程的 UMS 上下文调用 ExecuteUmsThread 来运行工作线程。 UMS 工作线程运行,直到它通过调用 UmsThreadYield 函数、块或终止生成。

UMS 最佳做法

实现 UMS 的应用程序应遵循以下最佳做法:

  • UMS 线程上下文的基础结构由系统管理,不应直接修改。 请改用 QueryUmsThreadInformationSetUmsThreadInformation 来检索和设置有关 UMS 工作线程的信息。
  • 为了帮助防止死锁,UMS 计划程序线程不应与 UMS 工作线程共享锁。 这包括应用程序创建的锁和系统锁,这些锁通过从堆分配或加载 DLL 等操作间接获取。 例如,假设计划程序运行加载 DLL 的 UMS 工作线程。 工作线程获取加载程序锁和块。 系统调用计划程序入口点函数,然后加载 DLL。 这会导致死锁,因为加载程序锁已被保留,在解除阻止第一个线程之前无法释放。 为帮助避免此问题,请将可能与 UMS 工作线程共享锁的工作委托给专用 UMS 工作线程或非 UMS 线程。
  • 当大多数处理是在用户模式下完成时,UMS 效率最高。 请尽可能避免在 UMS 工作线程中进行系统调用。
  • UMS 工作线程不应假定正在使用系统计划程序。 这种假设可能会产生微妙的影响:例如,如果未知代码中的线程设置了线程优先级或关联,则 UMS 计划程序仍可能替代它。 假设正在使用系统计划程序的代码可能无法按预期运行,并且在由 UMS 线程调用时可能会中断。
  • 系统可能需要锁定 UMS 工作线程的线程上下文。 例如, (APC) 的内核模式异步过程调用可能会更改 UMS 线程的上下文,因此必须锁定线程上下文。 如果计划程序尝试在 UMS 线程上下文锁定时执行它,则调用将失败。 此行为是设计使然,计划程序应设计为重试对 UMS 线程上下文的访问。