[转]使用VC/MFC创建一个线程池
许多应用程序创建的线程花费了大量时间在睡眠状态来等待事件的发生。还有一些线程进入睡眠状态后定期被唤醒以轮询工作方式来改变或者更新状态信息。线程池可以让你更有效地使用线程,它为你的应用程序提供一个由系统管理的工作者线程池。至少会有一个线程来监听放到线程池的所有等待操作,当等待操作完成后,线程池中将会有一个工作者线程来执行相应的回调函数。
你也可以把没有等待操作的工作项目放到线程池中,用QueueUserWorkItem函数来完成这个工作,把要执行的工作项目函数通过一个参数传递给线程池。工作项目被放到线程池中后,就不能再取消了。
Timer-queue timers和Registered wait operations也使用线程池来实现。他们的回调函数也放在线程池中。你也可以用BindIOCompletionCallback函数来投递一个异步IO操作,在IO完成端口上,回调函数也是由线程池线程来执行。
当第一次调用QueueUserWorkItem函数或者BindIOCompletionCallback函数的时候,线程池被自动创建,或者Timer-queue timers或者Registered wait operations放入回调函数的时候,线程池也可以被创建。线程池可以创建的线程数量不限,仅受限于可用的内存,每一个线程使用默认的初始堆栈大小,运行在默认的优先级上。
线程池中有两种类型的线程:IO线程和非IO线程。IO线程等待在可告警状态,工作项目作为APC放到IO线程中。如果你的工作项目需要线程执行在可警告状态,你应该将它放到IO线程。
非IO工作者线程等待在IO完成端口上,使用非IO线程比IO线程效率更高,也就是说,只要有可能的话,尽量使用非IO线程。IO线程和非IO线程在异步IO操作没有完成之前都不会退出。然而,不要在非IO线程中发出需要很长时间才能完成的异步IO请求。
正确使用线程池的方法是,工作项目函数以及它将会调用到的所有函数都必须是线程池安全的。安全的函数不应该假设线程是一次性线程的或者是永久线程。一般来说,应该避免使用线程本地存储和发出需要永久线程的异步IO调用,比如说RegNotifyChangeKeyValue函数。如果需要在永久线程中执行这样的函数的话,可以给QueueUserWorkItem传递一个选项WT_EXECUTEINPERSISTENTTHREAD。
注意,线程池不能兼容COM的单线程套间(STA)模型。
为了更深入地讲解操作系统实现的线程池的优越性,我们首先尝试着自己实现一个简单的线程池模型。
代码如下:
1 /**//************************************************************************/
2 /**//* Test Our own thread pool. */
3 /**//************************************************************************/
4
5 typedef struct _THREAD_POOL
6 {
7 HANDLE QuitEvent;
8 HANDLE WorkItemSemaphore;
9
10 LONG WorkItemCount;
11 LIST_ENTRY WorkItemHeader;
12 CRITICAL_SECTION WorkItemLock;
13
14 LONG ThreadNum;
15 HANDLE *ThreadsArray;
16
17 }THREAD_POOL, *PTHREAD_POOL;
18
19 typedef VOID (*WORK_ITEM_PROC)(PVOID Param);
20
21 typedef struct _WORK_ITEM
22 {
23 LIST_ENTRY List;
24
25 WORK_ITEM_PROC UserProc;
26 PVOID UserParam;
27
28 }WORK_ITEM, *PWORK_ITEM;
29
30
31 DWORD WINAPI WorkerThread(PVOID pParam)
32 {
33 PTHREAD_POOL pThreadPool = (PTHREAD_POOL)pParam;
34 HANDLE Events[2];
35
36 Events[0] = pThreadPool->QuitEvent;
37 Events[1] = pThreadPool->WorkItemSemaphore;
38
39 for(;;)
40 {
41 DWORD dwRet = WaitForMultipleObjects(2, Events, FALSE, INFINITE);
42
43 if(dwRet == WAIT_OBJECT_0)
44 break;
45
46 //
47 // execute user's proc.
48 //
49
50 else if(dwRet == WAIT_OBJECT_0 +1)
51 {
52 PWORK_ITEM pWorkItem;
53 PLIST_ENTRY pList;
54
55 EnterCriticalSection(&pThreadPool->WorkItemLock);
56 _ASSERT(!IsListEmpty(&pThreadPool->WorkItemHeader));
57 pList = RemoveHeadList(&pThreadPool->WorkItemHeader);
58 LeaveCriticalSection(&pThreadPool->WorkItemLock);
59
60 pWorkItem = CONTAINING_RECORD(pList, WORK_ITEM, List);
61 pWorkItem->UserProc(pWorkItem->UserParam);
62
63 InterlockedDecrement(&pThreadPool->WorkItemCount);
64 free(pWorkItem);
65 }
66
67 else
68 {
69 _ASSERT(0);
70 break;
71 }
72 }
73
74 return 0;
75 }
76
77 BOOL InitializeThreadPool(PTHREAD_POOL pThreadPool, LONG ThreadNum)
78 {
79 pThreadPool->QuitEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
80 pThreadPool->WorkItemSemaphore = CreateSemaphore(NULL, 0, 0x7FFFFFFF, NULL);
81 pThreadPool->WorkItemCount = 0;
82 InitializeListHead(&pThreadPool->WorkItemHeader);
83 InitializeCriticalSection(&pThreadPool->WorkItemLock);
84 pThreadPool->ThreadNum = ThreadNum;
85 pThreadPool->ThreadsArray = (HANDLE*)malloc(sizeof(HANDLE) * ThreadNum);
86
87 for(int i=0; i<ThreadNum; i++)
88 {
89 pThreadPool->ThreadsArray[i] = CreateThread(NULL, 0, WorkerThread, pThreadPool, 0, NULL);
90 }
91
92 return TRUE;
93 }
94
95 VOID DestroyThreadPool(PTHREAD_POOL pThreadPool)
96 {
97 SetEvent(pThreadPool->QuitEvent);
98
99 for(int i=0; i<pThreadPool->ThreadNum; i++)
100 {
101 WaitForSingleObject(pThreadPool->ThreadsArray[i], INFINITE);
102 CloseHandle(pThreadPool->ThreadsArray[i]);
103 }
104
105 free(pThreadPool->ThreadsArray);
106
107 CloseHandle(pThreadPool->QuitEvent);
108 CloseHandle(pThreadPool->WorkItemSemaphore);
109 DeleteCriticalSection(&pThreadPool->WorkItemLock);
110
111 while(!IsListEmpty(&pThreadPool->WorkItemHeader))
112 {
113 PWORK_ITEM pWorkItem;
114 PLIST_ENTRY pList;
115
116 pList = RemoveHeadList(&pThreadPool->WorkItemHeader);
117 pWorkItem = CONTAINING_RECORD(pList, WORK_ITEM, List);
118
119 free(pWorkItem);
120 }
121 }
122
123 BOOL PostWorkItem(PTHREAD_POOL pThreadPool, WORK_ITEM_PROC UserProc, PVOID UserParam)
124 {
125 PWORK_ITEM pWorkItem = (PWORK_ITEM)malloc(sizeof(WORK_ITEM));
126 if(pWorkItem == NULL)
127 return FALSE;
128
129 pWorkItem->UserProc = UserProc;
130 pWorkItem->UserParam = UserParam;
131
132 EnterCriticalSection(&pThreadPool->WorkItemLock);
133 InsertTailList(&pThreadPool->WorkItemHeader, &pWorkItem->List);
134 LeaveCriticalSection(&pThreadPool->WorkItemLock);
135
136 InterlockedIncrement(&pThreadPool->WorkItemCount);
137 ReleaseSemaphore(pThreadPool->WorkItemSemaphore, 1, NULL);
138
139 return TRUE;
140 }
141
142 VOID UserProc1(PVOID dwParam)
143 {
144 WorkItem(dwParam);
145 }
146
147 void TestSimpleThreadPool(BOOL bWaitMode, LONG ThreadNum)
148 {
149 THREAD_POOL ThreadPool;
150 InitializeThreadPool(&ThreadPool, ThreadNum);
151
152 CompleteEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
153 BeginTime = GetTickCount();
154 ItemCount = 20;
155
156 for(int i=0; i<20; i++)
157 {
158 PostWorkItem(&ThreadPool, UserProc1, (PVOID)bWaitMode);
159 }
160
161 WaitForSingleObject(CompleteEvent, INFINITE);
162 CloseHandle(CompleteEvent);
163
164 DestroyThreadPool(&ThreadPool);
165 }
166
我们把工作项目放到一个队列中,用一个信号量通知线程池,线程池中任意一个线程取出工作项目来执行,执行完毕之后,线程返回线程池,继续等待新的工作项目。
线程池中线程的数量是固定的,预先创建好的,永久的线程,直到销毁线程池的时候,这些线程才会被销毁。
线程池中线程获得工作项目的机会是均等的,随机的,并没有特别的方式保证哪一个线程具有特殊的优先获得工作项目的机会。
而且,同一时刻可以并发运行的线程数目没有任何限定。事实上,在我们的执行计算任务的演示代码中,所有的线程都并发执行。
下面,我们再来看一下,完成同样的任务,系统提供的线程池是如何运作的。
1 /**//************************************************************************/
2 /**//* QueueWorkItem Test. */
3 /**//************************************************************************/
4
5 DWORD BeginTime;
6 LONG ItemCount;
7 HANDLE CompleteEvent;
8
9 int compute()
10 {
11 srand(BeginTime);
12
13 for(int i=0; i<20 *1000 * 1000; i++)
14 rand();
15
16 return rand();
17 }
18
19 DWORD WINAPI WorkItem(LPVOID lpParameter)
20 {
21 BOOL bWaitMode = (BOOL)lpParameter;
22
23 if(bWaitMode)
24 Sleep(1000);
25 else
26 compute();
27
28 if(InterlockedDecrement(&ItemCount) == 0)
29 {
30 printf("Time total %d second.\n", GetTickCount() - BeginTime);
31 SetEvent(CompleteEvent);
32 }
33
34 return 0;
35 }
36
37 void TestWorkItem(BOOL bWaitMode, DWORD Flag)
38 {
39 CompleteEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
40 BeginTime = GetTickCount();
41 ItemCount = 20;
42
43 for(int i=0; i<20; i++)
44 {
45 QueueUserWorkItem(WorkItem, (PVOID)bWaitMode, Flag);
46 }
47
48 WaitForSingleObject(CompleteEvent, INFINITE);
49 CloseHandle(CompleteEvent);
50 }
51
很简单,是吧?我们仅需要关注于我们的回调函数即可。但是与我们的简单模拟来比,系统提供的线程池有着更多的优点。
首先,线程池中线程的数目是动态调整的,其次,线程池利用IO完成端口的特性,它可以限制并发运行的线程数目,默认情况下,将会限制为CPU的数目,这可以减少线程切换。它挑选最近执行过的线程再次投入执行,从而避免了不必要的线程切换。
[转]使用VC/MFC创建一个线程池的更多相关文章
- Android 性能优化(16)线程优化:Creating a Manager for Multiple Threads 如何创建一个线程池管理类
Creating a Manager for Multiple Threads 1.You should also read Processes and Threads The previous le ...
- 创建一个线程池(java)
private ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("billService-poo ...
- ThreadPoolExecutor – Java Thread Pool Example(如何使用Executor框架创建一个线程池)
Java thread pool manages the pool of worker threads, it contains a queue that keeps tasks waiting to ...
- python创建一个线程和一个线程池
创建一个线程 1.示例代码 import time import threading def task(arg): time.sleep(2) while True: num = input('> ...
- 二 Java利用等待/通知机制实现一个线程池
接着上一篇博客的 一Java线程的等待/通知模型 ,没有看过的建议先看一下.下面我们用等待通知机制来实现一个线程池 线程的任务就以打印一行文本来模拟耗时的任务.主要代码如下: 1 定义一个任务的接口 ...
- ExecutorService实际上是一个线程池的管理工具
在Java5之后,并发线程这块发生了根本的变化,最重要的莫过于新的启动.调度.管理线程的一大堆API了.在Java5以后,通过Executor来启动线程比用 Thread的start()更好.在新特征 ...
- Java多线程01(Thread类、线程创建、线程池)
Java多线程(Thread类.线程创建.线程池) 第一章 多线程 1.1 多线程介绍 1.1.1 基本概念 进程:进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于 ...
- 死磕 java线程系列之自己动手写一个线程池
欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. (手机横屏看源码更方便) 问题 (1)自己动手写一个线程池需要考虑哪些因素? (2)自己动手写 ...
- 按照阿里巴巴规范创建Java线程池
前言 Executors Executors 是一个Java中的工具类.提供工厂方法来创建不同类型的线程池. 常用方法: 1.newSingleThreadExecutor 介绍:创建一个单线程的 ...
随机推荐
- 如何在 Arch Linux 的终端里设定 WiFi 网络
如果你使用的是其他 Linux 发行版 而不是 Arch CLI,那么可能会不习惯在终端里设置 WiFi.尽管整个过程有点简单,不过我还是要讲一下.在这篇文章里,我将带领新手们通过一步步的设置向导,把 ...
- 自动打包iOS项目
基于Lexrus的博文iOS-makefile,本文对自动打包涉及到的操作步骤以及理论基础进行了适当的补充. 请在阅读本文前先阅读<iOS makefile>.文章地址:http: ...
- jQuery EasyUI datagrid实现本地分页的方法
http://www.codeweblog.com/jquery-easyui-datagrid%e5%ae%9e%e7%8e%b0%e6%9c%ac%e5%9c%b0%e5%88%86%e9%a1% ...
- 编写自己的Acunetix WVS漏洞扫描脚本详细教程
AWVS提供了自定义的脚本编程接口,可是网上的资料很少,只有官方的几篇介绍和参考手册,最近研究了一下怎么编写AWVS的漏洞脚本来写一篇简单性的文章 本文以8.0为例,首先呢安装好Acunetix We ...
- Visual Studio Online Integrations-Testing
原文:http://www.visualstudio.com/zh-cn/explore/vso-integrations-directory-vs
- 解决Jquery对input file控件的onchange事件只生效一次的问题
如题,解决办法的代码如下: 1. $('#fileId').live('change',function(){ //逻辑添加.... }); 2. $('#fileId').change(functi ...
- [Angularjs]ng-switch用法
用法描述 ng-switch根据表达式的值显示或这隐藏对应部分.类似c#或者其他预览里面的switch用法.可以慢慢体会. 说道ng-switch就要说到子元素该怎么根据当前值进行变化.子元素可以通过 ...
- C++构造函数详解及显式调用构造函数
来源:http://www.cnblogs.com/xkfz007/archive/2012/05/11/2496447.html c++类的构造函数详解 ...
- django migration使用指南
转自: https://docs.djangoproject.com/en/1.8/topics/migrations/
- C++复数四则运算的实现
程序主要实现复数的加减乘,数乘,取共轭功能. 将所有函数都定义为了成员函数. 使用库函数atof将字符串转换为浮点型数据. 函数主要难点在于处理输入.由于需要判断输入是选择退出还是继续,所以用字符串来 ...