Windows线程池

文章来源:http://www.cnblogs.com/coser/archive/2012/03/10/2389264.html

什么是线程池?

诸如web服务器、数据库服务器、文件服务器和邮件服务器等许多服务器应用都面向处理来自某些远程来源的大量短小的任务。构建服务器应用程序的一个过于简单的模型是:每当一个请求到达就创建一个新的服务对象,然后在新的服务对象中为请求服务。但当有大量请求并发访问时,服务器不断的创建和销毁对象的开销很大。所以提高服务器效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁,这样就引入了“池”的概念,“池”的概念使得人们可以定制一定量的资源,然后对这些资源进行复用,而不是频繁的创建和销毁。

线程池是预先创建线程的一种技术。线程池在还没有任务到来之前,创建一定数量的线程,放入空闲队列中。这些线程都是处于睡眠状态,即均为启动,不消耗CPU,而只是占用较小的内存空间。当请求到来之后,缓冲池给这次请求分配一个空闲线程,把请求传入此线程中运行,进行处理。当预先创建的线程都处于运行状态,即预制线程不够,线程池可以自由创建一定数量的新线程,用于处理更多的请求。当系统比较闲的时候,也可以通过移除一部分一直处于停用状态的线程。

线程池的注意事项

虽然线程池是构建多线程应用程序的强大机制,但使用它并不是没有风险的。在使用线程池时需注意线程池大小与性能的关系,注意并发风险、死锁、资源不足和线程泄漏等问题。

(1)线程池大小。多线程应用并非线程越多越好,需要根据系统运行的软硬件环境以及应用本身的特点决定线程池的大小。一般来说,如果代码结构合理的话,线程数目与CPU 数量相适合即可。如果线程运行时可能出现阻塞现象,可相应增加池的大小;如有必要可采用自适应算法来动态调整线程池的大小,以提高CPU 的有效利用率和系统的整体性能。

(2)并发错误。多线程应用要特别注意并发错误,要从逻辑上保证程序的正确性,注意避免死锁现象的发生。

(3)线程泄漏。这是线程池应用中一个严重的问题,当任务执行完毕而线程没能返回池中就会发生线程泄漏现象。

简单线程池的设计

一个典型的线程池,应该包括如下几个部分:
1、线程池管理器(ThreadPool),用于启动、停用,管理线程池
2、工作线程(WorkThread),线程池中的线程
3、请求接口(WorkRequest),创建请求对象,以供工作线程调度任务的执行
4、请求队列(RequestQueue),用于存放和提取请求
5、结果队列(ResultQueue),用于存储请求执行后返回的结果

线程池管理器,通过添加请求的方法(putRequest)向请求队列(RequestQueue)添加请求,这些请求事先需要实现请求接口,即传递工作函数、参数、结果处理函数、以及异常处理函数。之后初始化一定数量的工作线程,这些线程通过轮询的方式不断查看请求队列(RequestQueue),只要有请求存在,则会提取出请求,进行执行。然后,线程池管理器调用方法(poll)查看结果队列(resultQueue)是否有值,如果有值,则取出,调用结果处理函数执行。通过以上讲述,不难发现,这个系统的核心资源在于请求队列和结果队列,工作线程通过轮询requestQueue获得人物,主线程通过查看结果队列,获得执行结果。因此,对这个队列的设计,要实现线程同步,以及一定阻塞和超时机制的设计,以防止因为不断轮询而导致的过多cpu开销。在本文中,将会用python语言实现,python的Queue,就是很好的实现了对线程同步机制。

参考案例:

一个Windows C++的线程类实现
http://blog.csdn.net/huyiyang2010/article/details/5801597

一个Windows C++的线程池的实现
http://blog.csdn.net/huyiyang2010/article/details/5809919

一份C++线程池的代码,非常实用
http://blog.csdn.net/revv/article/details/3248424

Windows下一个比较完美的线程池实现
http://blog.csdn.net/fishjam


C#线程池
http://blog.csdn.net/binyao02123202/article/details/6564199

C#线程池用法
http://blog.csdn.net/rrrfff/article/details/7216802


使用VC/MFC创建一个线程池

http://blog.sina.com.cn/s/blog_703eaaad0100nmlj.html

有许多应用程序创建的线程花费了大量时间在睡眠状态来等待事件的发生。还有一些线程进入睡眠状态后定期被唤醒以轮询工作方式来改变或者更新状态信息。线程池可以让你更有效地使用线程,它为你的应用程序提供一个由系统管理的工作者线程池。至少会有一个线程来监听放到线程池的所有等待操作,当等待操作完成后,线程池中将会有一个工作者线程来执行相应的回调函数。
你也可以把没有等待操作的工作项目放到线程池中,用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    
  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(2Events, 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, 00x7FFFFFFFNULL);
 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=0i<ThreadNum; i++)
 88        {
 89            pThreadPool->ThreadsArray[i] = CreateThread(NULL, 0WorkerThread, pThreadPool, 0NULL);
 90        }
 91   
 92        return TRUE;
 93    }
 94   
 95    VOID DestroyThreadPool(PTHREAD_POOL pThreadPool)
 96    {
 97        SetEvent(pThreadPool->QuitEvent);
 98   
 99        for(int i=0i<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, 1NULL);
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=0i<20i++)
157        {
158            PostWorkItem(&ThreadPool, UserProc1, (PVOID)bWaitMode);
159        }
160       
161        WaitForSingleObject(CompleteEvent, INFINITE);
162        CloseHandle(CompleteEvent);
163   
164        DestroyThreadPool(&ThreadPool);
165    }
166 
我们把工作项目放到一个队列中,用一个信号量通知线程池,线程池中任意一个线程取出工作项目来执行,执行完毕之后,线程返回线程池,继续等待新的工作项目。
线程池中线程的数量是固定的,预先创建好的,永久的线程,直到销毁线程池的时候,这些线程才会被销毁。
线程池中线程获得工作项目的机会是均等的,随机的,并没有特别的方式保证哪一个线程具有特殊的优先获得工作项目的机会。
而且,同一时刻可以并发运行的线程数目没有任何限定。事实上,在我们的执行计算任务的演示代码中,所有的线程都并发执行。
下面,我们再来看一下,完成同样的任务,系统提供的线程池是如何运作的。微软提供的标准API函数,可使用QueueUserWorkItem连接方法(methods)到线程池。方法要运行在线程上,则必须把它连接到QueueUserWorkItem。如何实现呢?必须使用WaitCallback。在MSDN中,WaitCallback被描述成当线程池执行时要被调用的委托回调方法,是回调它的参数的委托。
 1  
 2    
 3    
 4   
 5    DWORD BeginTime;
 6    LONG   ItemCount;
 7    HANDLE CompleteEvent;
 8   
 9    int compute()
10    {
11        srand(BeginTime);
12   
13        for(int i=0i<20 *1000 * 1000i++)
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=0i<20i++)
44        {
45            QueueUserWorkItem(WorkItem, (PVOID)bWaitMode, Flag);
46           
47   
48        WaitForSingleObject(CompleteEvent, INFINITE);
49        CloseHandle(CompleteEvent);
50    }
51 

 

很简单,是吧?我们仅需要关注于我们的回调函数即可。但是与我们的简单模拟来比,系统提供的线程池有着更多的优点。
首先,线程池中线程的数目是动态调整的,其次,线程池利用IO完成端口的特性,它可以限制并发运行的线程数目,默认情况下,将会限制为CPU的数目,这可以减少线程切换。它挑选最近执行过的线程再次投入执行,从而避免了不必要的线程切换。

MSDN 参考

ThreadPool  VS  BackgroundWorker

如果你正在使用Windows窗体,宁可使用BackgroundWorker来对付那些更简单的线程需求,BackgroundWorker在网络访问和其他一些简单的事情方面做得很好。但对于多处理器的批处理来说,你需要ThreadPool。

BackgroundWorker 教程

当你的程序要批处理时,考虑线程池

当你的程序产生很多(3个以上)线程时,考虑线程池

当你的程序使用Windows窗体时,考虑后台执行。

线程要考虑的事 同样,如何使用线程的细节能帮助发现最好的代码。下面比较线程情形和哪个类是最好的。

   你需要一个额外的线程   使用后台执行

   你有许多短期的线程     使用线程池 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值