吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 458|回复: 1
收起左侧

[学习记录] Windows 线程池

[复制链接]
yg2pojie 发表于 2023-3-27 09:31
本帖最后由 yg2pojie 于 2023-3-27 09:33 编辑

Windows线程池
该部分主要是总结与Windows核心编程中相关内容,因为是从笔记中直接复制过来,所以格式可能有点乱,可以查看附件中的图片,里面格式较为完善
1、简介

  • Windows线程池是Windows操作系统提供的一种线程管理机制,它可以帮助开发者在应用程序中更有效地使用系统资源。Windows线程池的主要作用是管理一组预先创建好的线程,这些线程可以用来执行应用程序中的异步操作。当应用程序需要执行一个异步操作时,它可以向线程池提交一个任务,线程池会从池中选择一个可用的线程来执行该任务。当任务执行完成后,该线程会返回到线程池中,等待下一个任务的到来。


    • 每个进程都会有默认的线程池
  • 通过使用线程池,应用程序可以避免频繁地创建和销毁线程,从而减少了系统开销和内存使用,并提高了应用程序的响应性能和吞吐量。此外,线程池还提供了一些高级功能,例如动态调整线程池的大小、设置线程的优先级和取消正在执行的任务等。在Windows操作系统中,线程池可以通过使用Windows API函数来创建和管理,例如CreateThreadpool()、     SetThreadpoolThreadMaximum()、     SetThreadpoolThreadMinimum()等。开发者可以使用这些函数来创建和配置线程池,向线程池提交任务,并监控线程池的状态。


    • 如果线程池检测到它的线程数量己经供过于求,那么它就会销毁其中一些线程
  • 这些新的线程池函数允许我们做以下这些事情:


    • 以异步的方式来调用一个函数
    • 每隔一段时间调用一个函数
    • 当内核对象触发的时候调用一个函数
    • 当异步I/O请求完成的时候调用一个函数
2、情形1:以异步的方式调用函数

  • 用线程池来以异步的方式执行一个函数(回调函数),我们需要定义一个具有以下原型的函数:
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg

  • 为了让线程池中的一个线程执行该函数,我们需要向线程池提交一个请求,我们需要调用下面的函数:
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image004.jpg

  • 该函数(在内部通过调用PostQueuedCompletionStatus来)将一个工作项添加到线程池的队列中,若调用成功,则返回TRUE,若调用失败,则返回FALSE
  • 系统会自动为我们的进程创建一个默认的线程池,并让线程池中的一个线程来调用我们的回调函数

  • 显示的控制工作项


    • 在某些情况下,比如内存不足或配额限制,TrySubmitThreadpoolCallback 调用可能会失败。比如在创建一个计时器的时候和计时器设定的时间到的时候,可用的内存或配额的情况与创建定时器的时候可能并不相同,因此TrySubmitThreadpoolCallback有可能失败。在这种情况先,我们必须在创建定时器的同时就创建一个工作项对象,并一直持有它,知道我们需要显示的将该工作项提交到线程池中为止
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image006.jpg

  • 该函数会在用户模式内存中创建一个结构来保存它的三个参数,并返回指向该结构的指针(PTP_WORK)
  • pfnwk:是一个函数指针,当线程池中的线程最终对工作项进行处理的时候,会调用该函数指针指向的函数。pv:可以是需要传给回调函数的任意


    • 必须符合下列函数原型:
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image008.jpg

  • 向线程池提交一个请求的时候,可以调用SubmitThreadpoolWork函数
  • 如果我们有另一个线程,该线程想要取消已经提交的工作项,或者该线程由于要等待工作项处理完毕而需要将自己挂起,那么可以调用下面的函数
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image010.jpg

  • 第二个参数传TRUE,那么该函数会试图取消先前提交的那个工作项。传FALSE,那么该函数会将调用线程挂起,直到指定工作项的处理已经完成

  • 不在需要一个工作项的时候,我们应该调用CloseThreadpoolWork并在它唯一的参数中传入指向该工作项的指针
3、情形2:每隔一段时间调用一个函数

  • 为了将一个工作项安排在某个时间执行,我们必须定义一个回调函数,它的函数原型如下:
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image012.jpg

  • 然后调用下面的函数来通知线程池应该在何时调用我们的函数
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image014.jpg

  • 当我们想要向线程池注册计时器的时候,应该调用SetThreadpoolTimer函数
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image016.jpg

  • 在设置了计时器之后,我们还可以通过调用SetThreadpoolTimer并在pTimer参数中传入先前设置的计时器指针,对已有的计时器进行修改,重新传NULL给pftDueTime,这等于是告诉线程池停止调用我们的TimerCallback函数

  • 我们可以调用IsThreadpoolTimerSet来确定某个计时器是否已经被设置


    • 我们可以通过调用WaitForThreadpoolTimerCallbacks来让线程等待一个计时器完成,还可以通过调用CloseThreadpoolTimer函数来释放计时器的内存
4、情形3:在内核对象触发时调用一个函数

  • 如果想要注册一个工作项,让他在一个内核对象被触发的时候执行。首先,编写一个符合下面的原型的函数:
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image018.jpg

  • 回调函数被调用的原因:
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image020.jpg

  • 通过调用CreateThreadpoolWait来创建一个线程池等待对象


    • 当创建完成后,我们调用SetThreadpoolWait函数来将一个内核对象绑定到这个线程池:
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image022.jpg

  • 当内核对象被触发,等待时间到或超时,会导致线程池中的某个线程调用我们的WaitCallback函数。线程池在内部会让一个线程调用WaitForMultipleObjects函数,传入通过SetThreadpoolWait函数注册的一组句柄,并传FALSE给bWaitAll参数。这样当任何一个句柄被触发的时候,线程池就会被唤醒,WaitForMultipleObjects一次最多只能等待64个句柄

  • 我们可以通过调用WaitForThreadpoolWaitCallbacks函数来等待一个等待项完成,我们还可以通过调用CloseThreadpoolWait函数来释放一个等待项的内存
5、情形4:在异步I/O请求完成时调用一个函数

  • 当发往文件/设备的异步I/O请求完成时,应该调用一个符合下列原型的函数
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image024.jpg

  • 当一个I/O操作完成的时候,这个函数会被调用并得到一个指向OVERLAPPED结构的指针,这个指针是我们在调用ReadFile或WriteFile来发出I/O请求的时候传入,IoResult会得到操作的结果,如果I/O成功,那么该参数为NO_ERROR,NumberOfBytesTransferred得到已传输字节数,pIo:传入的是一个指向线程池I/O项的指针
  • 通过调用CreateThreadpoolIo来创建一个线程池I/O对象,并将我们想要与线程池内部的I/O完成端口相关联的文件/设备句柄,在第一个参数中传入
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image026.jpg

  • 当线程池I/O对象创建完毕后,我们通过下面函数来将嵌入在I/O项中的文件/设备与线程池内部的I/O完成端口关联起来
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image028.jpg

  • 注意,在每次调用ReadFile和WriteFile之前,我们必须调用StartThreadpoolIo,如果没有调用,回调函数将不会执行
如果想在发出I/O请求之后让线程池停止调用我们的回调函数,我们可以调用下面的函数:file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image030.jpg

  • 当文件/设备的使用完成后,我们应该调用CloseHandle来将其关闭,并调用下面函数来解除它与线程池的关联:
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image032.jpg

  • 我们还可以调用下面的函数来让另一个线程等待     待处理 的I/O请求 完成:
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image034.jpg

  • 第二个参数:指示是否取消尚未开始执行的排队回调
6、回调函数的终止操作

  • 线程池提供了一种方法,用来描述在我们的回调函数返回之后,应该执行的一些操作。回调函数用传给它的不透明的plnstance参数(其类型为PTP_CALLBACK_INSTANCE)来调用以下这些函数
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image036.jpg

  • 正如pInstance参数的名字所暗示的那样,他表示线程当前正在处理的一个工作项、计时器项、等待项或I/O项
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image038.jpg

  • 前4个函数为我们提供了一种方式通知另一个线程,线程池中的线程的工作项已经完成了某项任务。最后一个函数为我们提供了一种方式,让我们可以在回调函数返回的时候将动态链接库(DLL)从内存中卸载。如果回调函数是在一个DLL中实现的,而我们又希望在回调函数完成它的工作之后将DLL从内存中卸载,那么这种方式尤其有用
  • 对任何一个回调函数的实例,线程池中的线程只会执行一种终止操作。最后调用的终止函数会覆盖之前调用的那个终止函数

  • 除了上述的终止函数,还有两个函数可用于回调函数
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image040.jpg

  • CallbackMayRunLong函数,更多地是用来通知线程池回调函数的运行时间会比较长。让线程池采用一些操作
  • DisassociateCurrentThreadFromCallback函数,回调函数调用它来告诉线程池,逻辑上自己已经完成了工作。这使得任何由于调用WaitForThreadpooIWorkCallbacks等等待函数而被阻塞的线程能够早一些返回
7、对线程池进行定制

  • 在调用CreateThreadpooIWork等的时候,我们有机会传入一个PTP_CALLBACK_ENVIRON参数。如果传给这个参数的值为NULL,那么我们会将工作项添加到进程默认的线程池中,默认的线程池的配置能够很好地满足大多数应用程序的要求。我们也可以通过将该参数与某个我们创建的线程池相关联,将其传入,将工作项添加到我们创建的线程池中。下面的内容主要介绍如何创建线程池,以及如何将线程池与回调环境相关联
  • 我们可以自己调用函数来创建一个新的线程池:
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image042.jpg

  • 该函数返回一个PTP_POOL值,表示新创建的线程池,我们可以调用下面的函数来设置线程池中线程的最大数量(500)和最小数量(1)
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image044.jpg

  • 在一些少见的情况下,如果要索取信息的线程终止了,那么Windows会取消相关的请求,以RegNotifyChangeKeyValue函数为例,当线程调用这个函数的时候,会传入一个事件句柄,当某些注册表的值被修改的时候,Windows会触发该事件。但是,如果调用该函数的线程被终止了,那么Windows将不会再触发该事件
  • 只要线程池认为创建或销毁该线程有助于提高性能,他们就会这样做,针对此有两种解决方案:


    • 用CreateThread来创建一个专门的线程,这个线程不会终止,它的唯一目的就是调用RegNotifyChangeKeyValue函数
    • 创建一个线程池,并将线程的最小数量和最大数量设为相同的值

  • 当应用程序不再需要它为自己定制的线程池时,应该调用CloseThreadpool将其销毁。线程池中当前正在处理队列中的项的线程会完成它们的处理并终止。此外,线程池的队列中所有尚未开始处理的项将被取消。
  • 一旦我们创建了自己的线程池,并指定了线程的最小数量和最大数量,我们就可以初始化一个回调环境,它包含了一些可应用于工作项的额外的设置或配置


    • 线程池回调环境数据结构:
  typedef struct_TP_CALLBACK_ENVIRON_V3  {          TP_VERSION                         Version;          PTP_POOL                           Pool;          PTP_CLEANUP_GROUP                  CleanupGroup;          PTP_CLEANUP_GROUP_CANCEL_CALLBACK  CleanupGroupCancelCallback;          PVOID                              RaceDll;          struct_ACTIVATION_CONTEXT        *ActivationContext;          PTP_SIMPLE_CALLBACK                FinalizationCallback;          union{                  DWORD                          Flags;                  struct{                          DWORD                      LongFunction :1;                          DWORD                      Persistent   :1;                          DWORD                      Private      :30;                  }s;          }u;          TP_CALLBACK_PRIORITY               CallbackPriority;          DWORD                              Size;  }TP_CALLBACK_ENVIRON_V3;     typedef TP_CALLBACK_ENVIRON_V3  TP_CALLBACK_ENVIRON, *PTP_CALLBACK_ENVIRON;  

  • 为了对这个数据结构进行初始化,我们应该首先调用下面的函数
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image046.jpg

  • 当我们不再需要使用线程池回调环境的时候,我们应该调用DestroyThreadpoolEnvironment来对它进行清理

  • 为了将一个工作项添加到线程池的队列中,回调环境必须注明该工作项由哪个线程池来处理。我们可以调用SetThreadpoolCallbackPool并传给他一个PTP_POOL值,来指定一个特定的线程池
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image048.jpg

  • 当我们不调用该函数,那么TP_CALLBACK_ENZVIRON的Pool字段会一直为NULL,当用这个回调环境来添加工作项的时候,工作项会被添加到进程默认的线程池
8、得体的销毁线程池:清理组

  • 为了帮助我们对线程池进行得体的清理,线程池提供了清理组。下面讨论的内容不适用于默认的线程池,默认的线程池的生命期与进程相同,在进程终止的时候,Windows会将其销毁并负责所有的清理工作
  • 我们知道TR_CALLBACK_ENVIRON结构可以用来将队列添加到我们的私有线程池。为了得体的销毁私有线程池,我们需要通过调用CreateThreadpoolCleanupGroup来创建一个清理组,之后通过调用SetThreadpoolCallbackCleanupGroup,将这个清理组与一个已经绑定到线程池的TP_CALLBACK_ENVIRON结构关联起来,并可通过该函数传参设置一个回调函数(有原型),如果清理组被取消,那么这个回调函数会被调用


    • 每当我们调用CreateThreadpoolWork,CreateThreadpoolTimer等的时候,如果最后那个参数,即指向PTP_CALLBACK_ENVIRON结构的指针,不等于NULL,那么所创建的项会被添加到对应的回调环境的清理组中,其目的是为了表示有线程池中又添加了一项,需要潜在的清理。在这些队列项完成后,如果我们调用CloseThreadpoolWork,CloseThreadpoolTimer,CloseThreadpoolWait和CloseThreadpoolIo,那等于是隐式地将对应的项从清理组中移除。
  • 当我们想要销毁线程池的时候,可以调用下面函数,fCancelPendingCallbacks     传TRUE这样会将所有已提交但尚未处理的工作项直接取消,函数会在所有当前正在运行的工作项完成之后返回,并调用SetThreadpoolCallbackCleanupGroup设置的回调函数。传FALSE,在返回之前,线程池会花时间处理队列中所有剩余的项,这种情况下我们设置的     清理组回调函数绝对不会被调用
file:///C:/Users/lenovo/AppData/Local/Temp/msohtmlclip1/01/clip_image050.jpg

  • 所有的工作项被取消或处理之后,我们再调用CloseThreadpoolCleanupGroup来释放清理组所占用的资源
image.png

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Hmily 发表于 2023-3-27 17:13
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止回复与主题无关非技术内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-5-23 18:55

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表