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 }
开启线程和之前有所不同,这里可以显示更加灵活的控制。
运行效果和之前一样,这里就不做解释了。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。