当前位置:C++技术网 > 资讯 > 线程池学习总结:1 模拟实现线程池分析-创建线程池

线程池学习总结:1 模拟实现线程池分析-创建线程池

更新时间:2016-06-16 21:25:56浏览次数:1+次

    线程池这个东西,确实是一个好东西。让一堆线程一直活在池中,有事干的时候,出工干活,没事干的时候,睡觉。这是解决频繁创建和销毁线程带来巨大开销的一个解决方案。线程池特别适合应用在服务器端,比如http服务器等。每一个请求到来之后,很快就处理完了。如果对于每一个请求都创建一个线程来处理,处理完后,让线程结束。这是多线程。主线程一直不停的监听请求,然后将请求交给新创建的线程处理。这个相对于单线程来说,有了很大的改进,然而,反反复复的创建线程和销毁线程,会造成很大的开销。所以,很自然的想法就是,让线程不要那么快死去,最大化剥削线程的价值。举个例子来说,如果一有事情就让一个工人到工作间来,然后几分钟干完了,就让工人离开工作间。很快又有工作来了,又让工人进工作间,做完又出去。这样反反复复,从布置工作间,开始工作,工人离开后清理工作间。这些布置和清理工作如果反反复复的发生的话,确实非常浪费时间,而且工人来来回回的也很不爽,大量的时间精力花在了布置清理工作间上,老板没有得到好处,工人也没有得到好处,挺折腾人的。那么想到的办法就是,在工作间留一个休息间(线程池),早上上班的时候,把工作间布置好,然后让几个工人进来,然后工人没事就在休息间喝茶,事情来了,就去工作间干活,干完活又去休息间喝茶聊天。而工作间和休息间相隔几米,每人要用的工具自己携带,不能搁在工作间,否则会影响其他人工作。这样省去了大量的布置工作间的时间,以及让工人进来和出去的时间,这样效率就大大提升了。
    线程池就是这么个概念,其实并不难。只是,线程池是怎么实现的呢?线程如何在工作间和休息间切换呢?线程如何取得任务干活呢?其实这些问题,描述上很简单,然而要理解,还是有很多困难的。因为基本上很少有人很细致的解释每一个细节,包括思维上的小小的疑惑点。
    看到《Windows核心编程》中讲到线程池,就是介绍线程池函数的使用罢了,看的雾里看花,真心难得看懂线程池。为了理解这个线程池,我自己动手模拟一下,加深一下理解。自己去模拟实现,也就需要将线程池的基本模型实现出来。在代码层次中实现了一下,理解起来也就更加的清晰了。我们学习线程池,一定不是只是学习线程池API函数的使用,一定对线程池的机制非常清楚,其次才是API函数的使用。那么结合本次模拟的实现,将我的所思所想和代码分享分析出来,供大家学习参考。
    我们从头到尾来实现一下,从零开始,请跟着我的思路走,你一定也能掌握基本的线程池模型。然后你自己也可以实现。理解了之后,其实并不是很难得理解线程池,但是如果想做好,还是需要学习更多的知识的,本文只是一个引导而已。
    线程池是一组线程的活动的技术方案,说的是创建一组线程反复的去执行任务,而不是反复的【创建-执行-销毁】。最基本的问题就是要得到一组线程,并让他们不停的运转着,不能让线程死了。
    创建线程我们使用API函数CreateThread即可。CreateThread第一个参数传入NULL,表示使用默认的安全属性,这个现在不用去深究,一般都是使用默认的。第二个参数指定线程栈的大小,我们也使用默认的线程栈大小就可以了,所以传入0即可。第五个参数是指定线程创建后的行为,是立即执行还是挂起先,传入0,让线程立刻就可以执行。你也可以先让线程挂起来,然后等会再恢复。不过我们为了简化这个细节,将注意力放在整体上,就简单处理。详细的函数参数的使用,可以自己看MSDN的解释。这是几个基本的参数介绍。其他参数随着我们的思路一步步的介绍。
    创建线程我们已经知道怎么玩了。线程创建完后,怎么表现一个线程呢?也就是说,怎么让线程活动起来?有的人没有直观的感受,创建完线程后也不知道线程去哪了。线程是否结束或者是否正在运行,也不知道。实际上,这是对线程的运作并不熟悉而已。我在这里简单解释下。
    线程是执行代码的东西,是活跃的。执行代码自然就是执行我们写的代码生成的二进制机器指令。在我们看来,线程就是执行我们写的的代码,而通常线程创建后,必然要知道从哪里开始执行。所以我们需要指定一个函数让线程从指定的函数开始执行代码,至于最后执行到哪里去了呢,就看你代码怎么写了。不管怎么样,指定的这个函数执行完后,这个线程就结束了。所以,线程的生命周期就是你指定的这个线程函数。这个和主线程一样的,主线程就是执行main函数这个线程【控制台主线程】。所以,你指定给创建的线程执行的函数,也就是线程函数,也可以叫做线程入口函数。这个函数就是在CreateThread的第三个参数指定的,线程函数的声明如下:
DWORD WINAPI ThreadPoolProc(LPVOID lpParam);
    线程函数声明非常简单,就一个void指针参数,返回值为DWORD类型,WINAPI是调用函数的约定,线程函数名称随便你指定。看上去是不是非常简单呢,是的!那么想让线程不死去,很简单,死循环!一旦线程执行完线程函数的所有代码,线程就死了。让他不死就是不让他出去,圈养起来。死循环相信大家都会写,for、while随便。这样就解决了线程的创建和让线程|“永生”(一直留在线程池中)。那么你要线程池中有多少个线程,就创建多少个线程咯。就是多调用了几次CreateThread而已,没有增加难度哦。
    如果只是创建多个线程,以上就可以了。然而,你做一个线程池,自然不是简单丢几个线程进去就了事了哦。你需要管理,让线程们高效率的运作起来哦。为了能够轻松的控制线程,你需要得到线程的句柄,有了线程句柄,你可以杀死线程,挂起线程,恢复线程等等等等。所以在创建线程后,CreateThread会返回线程的句柄,所以你用数组存起来,以后可以方便使用。CreateThread会在最后一个参数中返回线程ID,这个ID作用不大,可以要可以不要,如果想看看线程ID,你就传入DWORD变量的地址接受线程ID值。当然多个线程你也可以用线程ID数组来存储。
    线程ID并不是从0开始这样的小数字,他是系统给的一个ID而已,其他线程结束后,ID就被系统回收了,然后又分配给其他线程使用。如果你想将几个线程从0开始编号,好区分的话,不能用线程ID。区分是在线程函数里输出显示出来的。我们经常要看看哪个线程做了什么,处于什么状态,我们在线程函数里输出线程的编号,然后输出执行状态就一目了然了。这个编号我们可以通过CreateThread的第四个参数传入,传入的参数会传到线程函数的void执行参数中,给线程函数使用。我们就可以在创建线程的时候传入线程函数,这样后续可以显示线程的编号了。
    说到这里,线程创建的所有参数随着我们的思路都讲解完毕了。在此,先看看创建一组线程的代码:
HANDLE hThread[5]={0};
DWORD dwThreadID[5]={0};
static int id[5];
for (int i=0;i<5;i++)
{
    id[i]=i;
    hThread[i]=CreateThread(NULL,0,ThreadPoolProc,&id[i],0,&dwThreadID[i]);
}
    看似简单的几句代码,我讲解了这么多,是按照思维的发展来一步步展开,让即使是新手也可以轻松的理解代码背后的意义,而不是简单的写几个代码糊里糊涂的了事。不过在此要注意,id数组使用的是静态的整型数组。因为我们是以指针形式传入到线程函数的,而不是传值的方式。CreateThread函数参数就是这样的,没得办法。当然,你可以使用全局变量传递,不过我不太喜欢,因为全局变量不好管理,容易混乱,谁都可以操作,到处都可以操作,在实际的开发中,看到全局变量是非常头疼的,你改代码也不敢轻举妄动,全局变量修改了,不知道会影响到多少地方。不过全局变量是一种线程间传递信息的方式。我这里是用静态变量传递的。为什么要用静态变量呢?你想想,变量地址传递过去之后,如果在线程没有开始执行之前,这边的变量就释放了,那么不是放人家鸽子嘛。说好的传递“情书”,人家还没有接受到,你就半路将情书烧了。因为你传递的是地址,不是变量值,你一搞小动作,就会影响。
    到此,创建线程部分就分析完了。模拟实现线程池,后面更加精彩,下一篇继续分析。