[转]使用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 介绍:创建一个单线程的 ...
随机推荐
- POJ2676Sudoku(类似于八皇后)
Sudoku Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 16444 Accepted: 8035 Special ...
- JavaScript的apply和call方法及其区别
参考资料: http://blog.csdn.net/myhahaxiao/article/details/6952321 apply和call能“劫持”其他对象的方法来执行,其形参如下: apply ...
- hdu 2084 数塔(动态规划)
本题是一个经典的动态规划题. 直接利用记忆化搜索:见图解 Ac code : #include<stdio.h> #include<string.h> #define max( ...
- 思维固化,addTarget难道就只能给self
使用前提 调用某个对象的,一个无参数的方法 如[self.view resignFirstResponder] 注意 [self.view endEdting:YES]就不行,要是无参数的
- mysql 外键(FOREIGN KEY)
最近有开始做一个实验室管理系统,因为分了几个表进行存储·所以要维护表间的关联··研究了一下MySQL的外键. (1)只有InnoDB类型的表才可以使用外键,mysql默认是MyISAM,这种类型不支持 ...
- VSS - 版本管理起的学习 AND 使用
局域网中用VSS.适用于Team级还可以,企业级不好,仅支持Windows 操作系统. Visual SourceSafe 是一个源代码控制系统,可以保存文件的不同版本,可以比较文件的差别,可以控制不 ...
- 将List<int> 转换为用逗号连接为字符串
List<, , , , }; string str = String.Join(",", testList.ConvertAll<string>(new Con ...
- 二分图最大匹配的König定理及其证明
二分图最大匹配的K?nig定理及其证明 本文将是这一系列里最短的一篇,因为我只打算把K?nig定理证了,其它的废话一概没有. 以下五个问题我可能会在以后的文章里说,如果你现在很想知道的话,网上 ...
- String的内存分配
1.String类是final类不能被继承 2.String str="abc"的内部工作 (1)先在栈中定 一个名为str的String类的引用变量 String str: (2 ...
- 使用 nginx + thin 的配置启动 rails server
http://www.iwangzheng.com 在大师的指导下配置了新的服务器的nginx,通过top命令查看了服务器是8个cpu的,所以起了8个端口,把它们都映射到一个总的端口3600上,需要在 ...