Windows线程池
Windows线程池
本文主要是参考 博客:http://blog.csdn.net/ithzhang/article/details/8373243 以及自己的一些心得而来。
我们自己也可以创建线程,但是涉及到线程的编码操作比较复杂,容易出现差错。为了简化程序员的工作,Windows提供了一个线程池机制来简化线程的创建、销毁以及日常管理。这个新线程池可能不适用于所有的情况,但大多数情况下它都能够满足我们的需要。
这个线程池能够帮助我们做一下事情:
一:以异步的方式调用一个函数。
二:每隔一段时间调用一个函数。
三:当内核对象触发时调用一个函数。
四:当异步IO请求完成时调用一个函数。
我们将在后面一一介绍上面各项。
一:以异步方式调用函数。
让线程池执行的函数需要遵循一下原型:
1 VOID NTAPI ThreadFunc(
2
3 PTP_CALLBACK_INSTANCE pInstance,
4
5 PVOID pvContext);
定义了线程池线程入口函数,就需要提交请求让线程池执行该函数:
1 BOOL TrySubmitThreadpoolCallback(
2
3 PTP_SIMPLE_CALLBACK pfnCallback,
4
5 PVOID pvContext,
6
7 PTP_CALLBACK_ENVIRON pche);
该函数将一个工作项添加到线程池队列中。若调用成功,则返回true。否则返回false。
pfnCallback表示线程池线程入口函数。即我们上面定义的函数。
pvContext是传给线程入口函数的参数。
pche可以先传给它NULL。在后面我们还会有详细的介绍。
当我们提交一个请求后,线程池就会创建一个默认的线程池并让线程池的一个线程来调用回调函数。并不需要我们手动调用CreateThread。当线程从入口函数返回时,并不会销毁而是返回到线程池。线程池会不断重复使用各个线程,而不会频繁销毁和新建线程。这显著的提高了性能。
在某些情况下,如内存不足时TrySubmitThreadpoolCallback可能会失败。第一次调用TrySubmitThreadpoolCallback时,系统会在内部分配一个工作项。如果打算提交大量的工作项,出于性能和内存使用方面的考虑,应该手动创建工作项然后多次提交
除了上面两个函数的实现方法外,还有另外的实现方法
下面的函数创建一个工作项:
1 PTP_WORK CreateThreadpoolWork(
2
3 PTP_WORK_CALLBACK pfnWorkHandler,
4
5 PVOID pvContext,
6
7 PTP_CALLBACK_ENVIRON pche);
pfnWorkHandler是一个函数指针,当线程池中的线程最终对工作项进行处理时会调用该函数。该函数必须遵循一下函数原型:
1 VOID CALLBACK WorkCallback(
2
3 PTP_CALLBACK_INSTANCE Instance,
4
5 PVOID Context,
6
7 PTP_WORK Work);
pvContext是传给pfnWorkHandler的参数。
我们可以调用SubmitThreadpoolWork向线程池提交一个请求:
1 VOID SubmitThreadpoolWork(PTP_WORK pWork);
如果我们项取消已经提交的工作项或是等待工作项处理完毕。可以调用以下函数:
VOID WaitForThreadpoolWorkCallbacks(
PTP_WORK pWork,
BOOL bCancelPendingCallbacks);
此函数将线程挂起,直到工作项处理完毕。
pWork指向工作项。此工作项可以是CreateThreadpoolWork和SubmitThreadpoolWork来创建和提交的。如果工作项尚未被提交,那么等待函数立即返回。
如果传入true给bCancelPendingCallbacks,那么WaitForThreadpoolWorkCallbacks会试图取消pWork标识的工作项。如果线程正在处理此工作项,则不会取消,而等待工作项处理完毕后返回。如果工作项还未被处理,函数会将此工作项标记为已取消,然后立即返回。
如果传入false给bCancelPendingCallbacks那么WaiForThreadpoolWorkCallbacks会将线程挂起,直到工作项处理完毕。
如果用一个PTP_WORK提交了多个工作项,传给bCancelPendingCallbacks为false,那么等待函数会等待所有的工作项都被处理完毕。
当不需要一个工作项时,可以调用CloseThreadpoolWork。
1 VOID CloseThreadpoolWork(PTP_WORK pwk);
ok,理论讲的差不多了,我们来点代码,实际操作操作吧
1.首先我们用以下两个函数的组合
1 VOID NTAPI ThreadFunc(
2
3 PTP_CALLBACK_INSTANCE pInstance,
4
5 PVOID pvContext);
1 BOOL TrySubmitThreadpoolCallback(
2
3 PTP_SIMPLE_CALLBACK pfnCallback,
4
5 PVOID pvContext,
6
7 PTP_CALLBACK_ENVIRON pche);
首我们建一个MFC的对话框程序,拖一个button控件和一个listbox控件
定义如下变量
1 public:
2 CListBox m_list;
3 DWORD m_CurrentTask;
添加一个用户自定义消息,关于用户自定消息这里就不做介绍了
#define TASK_COMPELETED WM_USER+1
1 public:
2 afx_msg LRESULT OnTaskCompeleted(WPARAM wparam,LPARAM lparam);
1 ON_MESSAGE(TASK_COMPELETED,&CwindowsThreadpoolTwoDlg::OnTaskCompeleted)
接下来是用户自定义消息的实现部分
1 LRESULT CwindowsThreadpoolTwoDlg::OnTaskCompeleted( WPARAM wparam,LPARAM lparam )
2 {
3 DWORD num=(DWORD)lparam;
4 CString s;
5 s.Format(TEXT("任务 [%d]执行完毕!"),num);
6 m_list.InsertString(-1,s);
7 return 0;
8 }
定义一个定义异步执行函数,线程执行时会自动调用该函数,这里是类成员函数,所以应该定义成静态函数
1 static void NTAPI SimpleCallBack(PTP_CALLBACK_INSTANCE pInstance,PVOID pvContext);
该函数的实现部分
1 void NTAPI CwindowsThreadpoolTwoDlg::SimpleCallBack(PTP_CALLBACK_INSTANCE pInstance,PVOID pvContext)
2 {
3 CwindowsThreadpoolTwoDlg*pdlg=(CwindowsThreadpoolTwoDlg*)pvContext;
4 InterlockedIncrement(&pdlg->m_CurrentTask);
5 DWORD num=pdlg->m_CurrentTask;
6 CString s;
7 s.Format(TEXT("【%d】任务%d开始运行!"),GetCurrentThreadId(),
pdlg->m_CurrentTask);
8 pdlg->m_list.InsertString(-1,s);
9 pdlg->PostMessage(TASK_COMPELETED,0,(LPARAM)num);
10 }
需要说一下InterlockedIncrement函数保证变量是以原子量方式增长的,实现线程同步。
现在我们来实现start按钮的实现部分
1 void CwindowsThreadpoolTwoDlg::OnBnClickedStart()
2 {
3 // TODO: 在此添加控件通知处理程序代码
4 BOOL rBet = TrySubmitThreadpoolCallback(SimpleCallBack,this,NULL);
5 if (!rBet)
6 {
7 AfxMessageBox(_T("向线程池提交申请失败“1”!"));
8 }
9
10 rBet = TrySubmitThreadpoolCallback(SimpleCallBack,this,NULL);
11 if (!rBet)
12 {
13 AfxMessageBox(_T("向线程池提交申请失败“2”!"));
14 }
15
16 rBet = TrySubmitThreadpoolCallback(SimpleCallBack,this,NULL);
17 if (!rBet)
18 {
19 AfxMessageBox(_T("向线程池提交申请失败“3”!"));
20 }
21
22 rBet = TrySubmitThreadpoolCallback(SimpleCallBack,this,NULL);
23 if (!rBet)
24 {
25 AfxMessageBox(_T("向线程池提交申请失败“4”!"));
26 }
27 }
很简单吧,我们来看看效果吧
可以在运行前,运行时,运行后一段时间内启动任务管理器看看线程数分别是多少。
可以看到运行前线程数为1,运行时线程数5,运行后线程数为1。
2.我们再试试另外一种方法,显示的控制工作项,我们用以下函数来完成。
1 VOID CALLBACK WorkCallback(
2
3 PTP_CALLBACK_INSTANCE Instance,
4
5 PVOID Context,
6
7 PTP_WORK Work);
1 PTP_WORK CreateThreadpoolWork(
2
3 PTP_WORK_CALLBACK pfnWorkHandler,
4
5 PVOID pvContext,
6
7 PTP_CALLBACK_ENVIRON pche);
1 VOID SubmitThreadpoolWork(PTP_WORK pWork);
我们还是和第一种实现一样,首先创建一个对话框,然后拖一个listbox和botton控件。
我们还是需要像一种方法一样定义一个自定义消息,这里我就只介绍不一样的地方了。
1 //定义工作项对象
2 PTP_WORK m_WorkItem;
1 //定义线程池异步执行函数
2 void static CALLBACK WorkCallback(PTP_CALLBACK_INSTANCE pInstance,PVOID pvContext,PTP_WORK Work);
注意这个函数和之前的函数相比,多了一个PTP_WORK 类型参数。
1 void CALLBACK CWindowsThreadPoolOneDlg::WorkCallback(PTP_CALLBACK_INSTANCE pInstance,PVOID pvContext,PTP_WORK Work)
2 {
3 CWindowsThreadPoolOneDlg*pdlg=(CWindowsThreadPoolOneDlg*)pvContext;
4 InterlockedIncrement(&pdlg->m_CurrentTask);
5 DWORD num=pdlg->m_CurrentTask;
6 CString s;
7 s.Format(TEXT("【%d】任务%d开始运行!"),GetCurrentThreadId(),pdlg->m_CurrentTask);
8 pdlg->m_list.InsertString(-1,s);
9 pdlg->PostMessage(TASK_COMPELETED,0,(LPARAM)num);
10 }
这个函数的实现还是和之前一样,不过名字换了一下。
1 void CWindowsThreadPoolOneDlg::OnBnClickedStart() 2 { 3 // TODO: 在此添加控件通知处理程序代码 4 5 m_WorkItem = ::CreateThreadpoolWork(ThreadFunC,this,NULL); 6 7 if (NULL == m_WorkItem) 8 { 9 AfxMessageBox(_T("工作项创建失败")); 10 return ; 11 } 12 SubmitThreadpoolWork(m_WorkItem); 13 SubmitThreadpoolWork(m_WorkItem); 14 SubmitThreadpoolWork(m_WorkItem); 15 SubmitThreadpoolWork(m_WorkItem); 16 }
开启线程和之前有所不同,这里可以显示更加灵活的控制。
运行效果和之前一样,这里就不做解释了。
情形二:每隔一段时间调用一个函数
有时候应用程序需要在某些时间执行某些任务。Windows提供了可等待计时器对象,它使我们我们可以非常方便的得到一个时间通知。我们可以为每个需要执行基于时间的任务创建一个可等待的计时器对象,但这是不必要的。线程池函数为我们解决了这些事情。
为了将一个工作项安排在某个时间执行,我们必须定义一个回调函数。该函数会在某个时刻被调用。回调函数原型为:
1 VOID CALLBACK TimeoutCallback(
2
3 PTP_CALLBACK_INSTANCE pInstance,
4
5 PVOID pvContext,
6
7 PTP_TIMER pTimer);
然后调用下面的函数来通知线程池应在何时调用我们的函数:
1 PTP_TIMER CreateThreadpoolTimer(
2
3 PTP_TIMER_CALLBACK pfnTimerCallback,
4
5 PVOID pvContext,
6
7 PTP_CALLBACK_ENVIRON pcbe);
这个函数与前面介绍的CreateThreadpoolWork相似。
pfnTimerCallback是一个函数指针。指向前面介绍的回调函数TimeroutCallback。每当线程池调用pfnTimerCallback指向的函数时会将pvContext传给它,并传给pTimer一个由CreateThreadpoolTimer返回的计时器对象指针。
pvContext为传给回调函数参数。
CreateThreadpoolTimer返回计时器对象。该计时器对象由CreateThreadpoolTimer函数创建并返回。
当我们想要向线程池注册计时器时,应该调用SetThreadpoolTimer:
1 VOID SetThreadpoolTimer(
2
3 PTP_TIMER pTimer,
4
5 PFILETIME pftDueTime,
6
7 DWORD msPeriod,
8
9 DWORD msWindowLength);
pTimer用来标识CreateThreadpoolTimer返回的计时器对象。
pftDueTime表示第一次调用回调函数是什么时候。传入一个负值表示一个相对时间。该时间相对于调用SetThreadpoolTimer的时间。传入-1表示立即调用。传入的正值以100ns为单位,从1600年的1月1日开始计算。
msPeriod表示调用回调函数的时间间隔,传入0表示只调用1次。
msWindowLength用来给回调函数的执行增加一些随机性。这使得回调函数会在当前设定的时间到当前设定的触发时间加上msWindowLength设定的时间之间触发。这对于多个计时器来说非常有用。这可以避免多个计时器间的冲突。
在设置计时器之后还可以再次调用SetThreadpoolTimer来对计时器进行修改。同时也可以调用IsThreadpoolSet来确定某个计时器是否已经被设置(即pfnDueTime不为NULL)。
1 BOOL IsThreadpoolTimerSet(PTP_TIMER pti);
最后我们可以通过WaitForThreadpoolTimerCallbacks来等待一个计时器完成。调用CloseThreadpoolTimer来释放计时器。它们与前面介绍的WaitForThreadpoolWork和CloseThreadpoolWork相似。
例子:
接下来我们写一例子,该例子演示如何使用每隔一段时间调用线程池。
首先我们新建一个MFC对话框程序,用鼠标拖一个button和一个listbox控件。
定义变量如下:
1 public:
2 //定义计时器对象
3 PTP_TIMER m_Timer;
4
5 ULONG m_Start;
6
7 CListBox m_list;
定义回调函数:
1 //定义回调函数
2 static VOID CALLBACK TimeoutCallback(
3 PTP_CALLBACK_INSTANCE pInstance,
4 PVOID pvContext,
5 PTP_TIMER pTimer);
在按钮事件里的实现
1 void CWindowsTreadpoolTimerDlg::OnBnClickedStart()
2 {
3 // TODO: 在此添加控件通知处理程序代码
4
5 m_Timer = CreateThreadpoolTimer(TimeoutCallback,this,NULL) ;
6 if (m_Timer == NULL)
7 {
8 AfxMessageBox(_T("创建PTP_TImer失败"));
9 }
10 FILETIME filetime;
11 LARGE_INTEGER li;
12 //传入-1设置后立即调用线程函数
13 li.QuadPart=(LONGLONG)-(10000000);
14 filetime.dwHighDateTime=li.HighPart;
15 filetime.dwLowDateTime=li.LowPart;
16 SetThreadpoolTimer(m_Timer,&filetime,1,0);
17 }
在回调函数的实现
1 VOID CALLBACK CWindowsTreadpoolTimerDlg::TimeoutCallback(
2 PTP_CALLBACK_INSTANCE pInstance,
3 PVOID pvContext,
4 PTP_TIMER pTimer)
5 {
6 CWindowsTreadpoolTimerDlg *pDlg = (CWindowsTreadpoolTimerDlg *)pvContext;
7 CString str ;
8 str.Format(_T("你还有%d秒!!!"),pDlg->m_Start);
9 //为了保证线程安全,让pDlg->m_Start以原子量方式递减,
10 InterlockedExchangeAdd((&pDlg->m_Start),-1);
11 pDlg->m_list.AddString(str);
12 }
运行结果:
大家可以更改 下面这个函数的第三个参数,然后打开任务管理器查看该demo的线程数。
1 VOID SetThreadpoolTimer(
2
3 PTP_TIMER pTimer,
4
5 PFILETIME pftDueTime,
6
7 DWORD msPeriod,
8
9 DWORD msWindowLength);
在我的电脑上设置第三个参数为1,1000,10000,时线程数分别为17,4,4。
大家也可以在线程的回调函数中弹出一个模态对话框而不销毁模态对话框,查看该demo的线程数。
我们会发现,每弹出一个未被销毁的模态对话框,线程数增加了1,因此该线程池还是比较智能的吧。
会根据用户的使用情况,自动开启或销毁线程。
情形三:在内核对象触发时调用一个函数:
在实际使用中我们会发现我们会经常的等待一个内核对象被触发,触发后等待线程又会进入下一轮循环继续等待。Windows线程池提供了一些机制可以简化我们的工作。
如果我们想让内核对象被触发时执行某函数。需要进行以下步骤:
首先编写一个回调函数,它是内核对象被触发时被调用的函数。需要满足一下原型:
1 VOID CALLBACK WaitCallback(
2 PTP_CALLBACK_INSTANCE pInstance,
3 PVOID Context,
4 PTP_WAIT Wait,
5 TP_WAIT_RESULT WaitResult);
6
然后创建CreateThreadpoolWait来将一个内核对象绑定到线程池:
1 VOID SetThreadpoolWait(
2 PTP_WAIT pWaitItem,
3 HANDLE hObject,
4 PFILETIME pftTimeout);
pWaitItem用来标识CreateTheadpoolWait返回的对象。
hObject用来标识内核对象。当此对象被触发时,回调函数会被调用。
pftTimeout用来表示线程池最长应该花多少时间来等待内核对象被触发。传入0表示不用等待。传入负值表示相对时间传NULL表示无限长的时间。
线程池内部会让一个线程调用WaitForMultipleOBjecs函数。传入SetThreadpoolWait函数注册的一组句柄,并传入false给bWaitAll参数。当任何一个内核对象被触发时,线程池就会被唤醒。
当内核对象被触发或是超出等待时间时,线程池的某个线程就会调用我们的回调函数。
WaitResult用来表示WaitCallback被调用的原因。它可以是以下值:
WAIT_OBJECT_0 超时之前有对象被触发。
WAIT_TIMEOUT 由于超时导致回调函数被触发。
WAIT_ABANDONED_0 如果传入的内核对象是互斥量且被遗弃。回调函数将收到这个值。
一旦线程池调用了我们的回调函数,对应的等待项将进入不活跃状态。所谓不活跃状态:如果想让回调函数在同一个内核对象被触发时再次被调用,我们需要调用SetThreadpoolWait来再次注册。
最后我们同样可以等待一个等待项完成。这可以调用WaitForThreadpoolWaitCallbacks。还可以调用CloseThreadpoolWait来释放一个等待项的内存。
注意:不要让回调函数调用WaitForThreadpoolWork并将自己的工作项作为参数传入,这会导致死锁。
我们也来看个例子吧。
首先建立一个MFC对话框程序。用鼠标拖一个listbox控件 和两个botton控件
下面是定义的变量:
1 public:
2 PTP_WAIT m_Wait;
3 HANDLE m_Event;
4 DWORD m_start;
5 //控件变量,用助手添加
6 CListBox m_list;
定义等待回调函数:
1 static VOID CALLBACK WaitCallback(
2 PTP_CALLBACK_INSTANCE pInstance,
3 PVOID Context,
4 PTP_WAIT Wait,
5 TP_WAIT_RESULT WaitResult);
在OnInitDialog里面添加如下代码
1 //创建一个线程池等待对象 2 m_Wait = CreateThreadpoolWait(WaitCallback,this,0); 3 //创建一个事件对象 4 m_Event = CreateEvent(NULL,TRUE,NULL,NULL); 5 //讲一个内核对象绑定到线程池 6 SetThreadpoolWait(m_Wait,m_Event,NULL); 7 SetEvent(m_Event);
8 m_start = 0 ;
start按钮实现
1 void CWindoswsThreadpoolWaitDlg::OnBnClickedStart()
2 {
3 // TODO: 在此添加控件通知处理程序代码态
4
5 6 SetThreadpoolWait(m_Wait,m_Event,NULL);
7 }
end按钮实现,其实有点多余,在本例中没有用到这个功能。
1 void CWindoswsThreadpoolWaitDlg::OnBnClickedEnd()
2 {
3 // TODO: 在此添加控件通知处理程序代码
5 ResetEvent(m_Event);
6 }
在等待回调函数的实现部分。
1 VOID CALLBACK CWindoswsThreadpoolWaitDlg::WaitCallback(
2 PTP_CALLBACK_INSTANCE pInstance,
3 PVOID Context,
4 PTP_WAIT Wait,
5 TP_WAIT_RESULT WaitResult)
6 {
7 CWindoswsThreadpoolWaitDlg* pDlg = (CWindoswsThreadpoolWaitDlg*)Context;
8 Sleep(1000);
9 pDlg->m_start++;
10 CString str;
11 str.Format(_T("这是第%d次了"),pDlg->m_start);
12 pDlg->m_list.AddString(str);
13
14 }
ok,这个代码运行就不做展示了,可以发现我们没点击一次start按钮,这个回调函数被调用一次。
情形四:在异步IO完成时调用一个函数。
我们在上一篇博文中介绍了如何使用IO完成端口来高效的执行异步IO操作,也介绍了如何创建一个线程池并让其中的线程等待IO完成端口。这里我们将介绍线程池如何管理线程的创建和销毁。
在打开一个关联起来文件或设备时,我们必须现将该设备与线程池的IO完成端口,然后告诉线程池在异步IO完成时应该调用哪个函数。
首先我们需要定义回调函数,它需要满足一下原型:
1 VOID CALLBACK OverlappedCompletionRoutine(
2
3 PTP_CALLBACK_INSTANCE pInstance,
4
5 PVOID pvContext,
6
7 PVOID pOverlapped,
8
9 ULONG IoResult;
10
11 ULONG_PTR NumberOfBytesTransferred,
12
13 PTP_IO pIo);
当一个IO操作完成时此回调函数会被调用并得到一个指向OVERLAPPED结构的指针。此结构是我们在调用ReadFile后WriteFile时传入的。
IoResult表示IO异步操作的执行结果。如果IO请求成功,将传给回调函数NO_ERROR。
NumberOfBytesTransferred参数传入已传输的字节数。
pIo传入指向线程池IO项的指针。马上介绍。
pInstance后面会有介绍。
定义好回调函数后,我们就需要调用CreateThreadpoolIo来创建一个线程池IO对象。
1 PTP_IO CreateThreadpoolIo(
2
3 HANDLE hDevice,
4
5 PTP_WIN32_IO_CALLBACK pfnIoCallback,
6
7 PVOID pvContext,
8
9 PTP_CALLBACK_ENVIRON pcbe);
hDevice是与IO对象相关联的设备句柄。
pfnIoCallback是前面我们介绍的回调函数指针。
pvContext当然是传给回调函数的参数。
当IO对象创建好之后,我们就可以通过下面的函数来将潜入在IO项的设备与IO完成端口关联起来。
1 VOID StartThreadpoolIo(PTP_IO pio);
关联之后我们就可以调用ReadFile或WriteFile了。此后当异步IO请求完成后,回调函数将会被调用。
此外我们还可以调用以下函数来停止线程池调用回调函数,此后回调函数将不会被调用:
1 VOID CancelThreadpoolIo(PTP_IO pio);
WaitForThreadpoolIoCallbacks将等待一个待处理的IO请求我完成。
1 VOID WaitForThreadpoolIoCallback(
2
3 PTP_IO pio,
4
5 BOOL bCancelPendingCallbacks);
如果传给bCancelPendingCallbacks的值为true,那么当请求完成时,回调函数不会被调用。
对线程池进行定制
在调用CreateThreadpoolWork、CreateThreadpoolTimer,CreateThreadpoolWait或CreateThreadpoolIo时,有一个PTP_CALLBACK_ENVIRON类型的参数。如果传给它NULL则表示我们会将工作项添加到默认的线程池中。一般情况下默认的线程池能够满足大多数情况下的要求。
如果我们想定制我们自己的线程池,可以调用CreateThreadpool来创建新线程池:
1 PTP_POOL CreateThreadpool(PVOID reserved);
Reserved是保留的,传入NULL即可。
该函数返回一个PTP_POOL值,它表示新创建的线程。
此后我们就可以设置线程池中的最大线程和最小线程了。默认的线程池中线程最少为1个,最多为500。
1 void SetThreadpoolThreadMinimum(PTP_POOL pThreadpool,DWORD cthrdMin);
2
3
4
5 void SetThreadpoolThreadMaximum(PTP_POOL pThreadpool,DWORD cthrdMin);
这仅仅是告诉线程池最大和最少线程个数。但实际使用中,线程池会非常智能的决定创建或是销毁线程。只要这样做提高性能有益。
当我们不需要自己定制的线程池时可以调用CloseThreadpool销毁。
1 VOID CloseThreadpool(PTP_POOL pThreadpool);
调用此函数后线程池所有的线程都被终止。线程池队列中所有尚未开始处理的项将被取消。
创建了线程池之后,并对线程池进行一定的配置后。我们就可以初始化线程池了,以便构造出一个回调环境。回调环境是一个PTP_CALLBACK_ENVIRON结构。该结构中包含了线程池、清理组等字段。我们可以调用InitializeThreadpoolEnvironment对其进行初始化:
-
1 VOID InitializeThreadpoolEnvironment( 2 3 PTP_CALLBACK_ENVIRON pcbe);
该函数会将回调环境的版本字段置为1,其余字段为0.
初始化回调环境后,还需要将其与线程池进行关联:
-
1 VOID SetThreadpoolCallbackpoo( 2 3 PTP_CALLBACK_ENVIRON pche, 4 5 PTP_POOL pThreadPool);
该函数将回调环境的线程池字段置为pThreadpool指向的线程池。当不调用此函数时,PTP_CALLBACK_ENVIRON结构的线程池字段一直为NULL,当用这个回调环境来添加工作项时,工作项会被添加到默认的线程池中。
当我们不需要回调环境时可以调用DestroyThreadEnvironment来将其销毁:
-
1 VOID DestroyThreadEnvironment(PTP_CALLBACK_ENVRION pcbe);
线程池同样提供了将自己销毁的机制。默认的线程池不会被销毁,它与进程的生命期一样长。进程结束后,Windows将负责所有的清理操作。
要想对我们自定义的线程池执行清理操作,首先需要创建一个清理组:
-
1 PTP_CLEANUP_GROUP CreateThreadpoolCleanuGroup();
创建清理组后还需要将清理组和线程池相关联:
-
1 VOID SetThreadpoolCallbackCleanupGroup( 2 3 PTP_CALLBACK_ENVIRON pcbe, 4 5 PTP_CLEANUP_GROUP ptpcg, 6 7 PTP_CLEANUP_GROUP_CANCEL_CALLBACK pfng);
此函数会设置回调环境的清理组字段为ptpcg指定的清理组。
pfng指向一个回调函数。当清理组被取消时回调函数将会被调用。该回调函数必须满足一下原型:
-
1 VOID CALLBACK CleanupGroupCancelback( 2 3 PVOID pvObjectContext, 4 5 PVOID pvCleanupContext);
当我们调用CreateThreadpoolWork,CreateThreadpoolTimer,CreateThreadpoolWait或CreateThreadpoolIo时,如果最后的指向PTP_CALLBACK_ENVIRON结构的指针不为NULL,那么所创建的项会被添加到对应的回调函数清理组中。它表示线程池中又添加了一项,需要清理。当我们调用CloseThreadpoolWork,CloseThreadpoolTimer,CloseThreadpoolWait或是CloseThreadIo时,等于是隐式的将对应项从清理组中清理。
当我们想清理线程池时可以调用:
-
1 VOID CloseThreadpoolCleanupGroupMemebers( 2 3 PTP_CLEANUPGROUP ptpcg, 4 5 BOOL bCancelPendingCallbacks, 6 7 PVOID pvCleanupContext);
如果传入false给bCancelPendignCallbacks,调用此函数的线程会一直等待直到线程池工作组中所有剩余的项当已经处理完毕为止。当传入true给bCancelPendingCallbacks时,所有已提交但未处理的工作项将直接被取消。对于每一个待处理的项,pfng指向的回调函数将会被调用。
当所有的工作项都被取消或被处理后,可以调用CloseThreadpoolCleanupGroup来释放清理组所占的资源:
1 VOID WINAPI CloseThreadpoolCleanupGroup(
2
3 PTP_CLEANUP_GROUP ptpcg);