线程池

线程池是代表应用程序高效执行异步回调的工作线程集合。 线程池主要用于减少应用程序线程数并提供工作线程的管理。 应用程序可以对工作项进行排队、将工作与可等待句柄相关联、基于计时器自动排队,并使用 I/O 绑定。

线程池体系结构

以下应用程序可以从使用线程池中获益:

  • 高度并行且可以异步 (调度大量小型工作项的应用程序,例如分布式索引搜索或网络 I/O) 。
  • 创建和销毁大量线程的应用程序,每个线程运行时间很短。 使用线程池可以减少线程管理的复杂性以及线程创建和销毁所涉及的开销。
  • 在后台和并行 (处理独立工作项的应用程序,例如) 加载多个选项卡。
  • 必须对内核对象执行独占等待或对对象上的传入事件执行阻止的应用程序。 使用线程池可以降低线程管理的复杂性,并通过减少上下文切换的数量来提高性能。
  • 一个应用程序,用于创建自定义服务员线程来等待事件。

原始线程池已在 Windows Vista 中完全重新构建。 新线程池已得到改进,因为它提供单个工作线程类型, (支持 I/O 和非 I/O) ,不使用计时器线程,提供单个计时器队列,并提供专用的永久性线程。 它还提供清理组、更高的性能、每个进程独立计划的多个池,以及新的线程池 API。

线程池体系结构包括以下内容:

  • 执行回调函数的工作线程
  • 在多个等待句柄上等待的等待线程
  • 工作队列
  • 每个进程的默认线程池
  • 管理工作线程的工作器工厂

最佳实践

与原始 线程池 API 相比,新的 线程池 API 提供了更大的灵活性和控制。 但是,存在一些微妙但重要的差异。 在原始 API 中,等待重置是自动的;在新 API 中,每次都必须显式重置等待。 原始 API 自动处理模拟,将调用进程的安全上下文传输到线程。 使用新 API 时,应用程序必须显式设置安全上下文。

下面是使用线程池时的最佳做法:

  • 进程的线程共享线程池。 单个工作线程可以一次执行多个回调函数。 这些工作线程由线程池管理。 因此,不要通过在线程上调用 TerminateThread 或从回调函数调用 ExitThread 来终止线程池中的线程。

  • I/O 请求可以在线程池中的任何线程上运行。 取消线程池线程上的 I/O 需要同步,因为取消函数可能在与处理 I/O 请求的线程不同的线程上运行,这可能会导致取消未知操作。 若要避免这种情况,请始终提供在调用 CancelIoEx 进行异步 I/O 时启动 I/O 请求时使用的 OVERLAPPED 结构,或使用你自己的同步来确保在调用 CancelSynchronousIoCancelIoEx 函数之前,无法在目标线程上启动其他 I/O。

  • 在从函数返回之前,请清理回调函数中创建的所有资源。 其中包括 TLS、安全上下文、线程优先级和 COM 注册。 回调函数还必须在返回之前还原线程状态。

  • 使等待句柄及其关联对象保持活动状态,直到线程池发出该句柄已完成的信号。

  • 标记所有正在等待长时间操作的线程 (,例如 I/O 刷新或资源清理) ,以便线程池可以分配新线程,而不是等待此线程。

  • 卸载使用线程池的 DLL 之前,请取消所有工作项、I/O、等待操作和计时器,并等待执行回调完成。

  • 通过消除工作项之间和回调之间的依赖关系、确保回调不会等待自身完成以及保留线程优先级来避免死锁。

  • 不要使用默认线程池与其他组件在进程中过快地将太多项排入队列。 每个进程都有一个默认线程池,包括Svchost.exe。 默认情况下,每个线程池最多有 500 个工作线程。 当处于就绪/正在运行状态的工作线程数必须小于处理器数时,线程池会尝试创建更多工作线程。

  • 避免使用 COM 单线程单元模型,因为它与线程池不兼容。 STA 创建可能影响线程的下一个工作项的线程状态。 STA 通常生存期较长,并且具有线程相关性,这与线程池相反。

  • 创建新的线程池来控制线程优先级和隔离,创建自定义特征,并可能提高响应能力。 但是,其他线程池需要更多系统资源 (线程、内核内存) 。 池太多会增加 CPU 争用的可能性。

  • 如果可能,请使用可等待对象而不是基于 APC 的机制向线程池线程发出信号。 APC 与其他信号机制一样适用于线程池线程,因为系统控制线程池线程的生存期,因此可以在传递通知之前终止线程。

  • 使用线程池调试器扩展 !tp。 此命令具有以下用法:

    • 地址标志
    • obj 地址标志
    • tqueue 地址标志
    • 服务员 地址
    • 辅助 角色地址

    对于池、服务员和辅助角色,如果地址为零,则命令将转储所有对象。 对于服务员和辅助角色,省略地址会转储当前线程。 定义了以下标志:0x1 (单行输出) 、0x2 (转储成员) ,以及0x4 (转储池工作队列) 。

线程池 API

使用线程池函数