当前位置:C++技术网 > 精选软件 > 线程池学习总结:2 模拟实现线程池分析-所有线程共同完成任务

线程池学习总结:2 模拟实现线程池分析-所有线程共同完成任务

更新时间:2016-06-16 22:39:57浏览次数:1+次

    在《线程池学习总结1:模拟实现线程池分析-创建线程池》中,我已经描述了线程池的概念,并做了比喻,相信基本上你是有一个大体的认识的。我们这里将进一步分析,让你的认识越来越具体,越来越清晰。真正的理解要非常的细致,了解到精髓才算学到了。大体的理解,远远不够。
    我们已经创建了一组线程,而且这些线程一创建好,就都会运行起来。我们拥有这组线程的线程句柄,并存储在一个数组中,我们可以通过线程句柄轻松控制线程们。同时,我们将给每一个线程传入一个序号,方便输出查看各个线程都在干什么。
    我们有一个问题,这些线程都指定一个线程函数吗?会不会冲突呢?可以指定为不同的函数吗?好的,这就是本文的主题,我们将对这个问题进行详细分析。线程函数的指定这一点,很多新手都会比较虚,他不知道所有线程指定同一个线程函数意味着什么,不指定为同一个函数意味着什么,大家公用一个线程函数会不会有问题,新手们全然不知,只能唯唯诺诺的用着,心里暗暗祈祷,程序不要出什么问题,否则也不知道如何解决。说的是你们,其实也是说的是我自己的感受。我们必须对每一个疑惑点或者担心的点深入的分析,彻底的了解了才能胸有成竹的说,“我的程序没有问题”。我想,想彻底搞清楚搞懂一个技术,也就是这样,糊里糊涂的似懂非懂,其实没有什么卵用。
    我们创建的多个线程,是有特定的目的的。我们不是让这些线程们简单的各自做各自的事情的,如果是这样的,那么线程池也没有什么意义了。我们创建多个线程形成一组,形成团队,是提高战斗力的!!所以,这些线程的目标是一致的,也就是所做的工作必然是一样的。这样的话,一个线程累趴下了,另外的线程立马可以上前顶替。如果工作量越来越大,我们只需要增派人手就行了,因为所有的线程目标一致,大家都做同样的工作,这样就可以为其他战友分担任务,加速任务的处理速度。我举个例子,如果一个士兵只会打炮,一个士兵只会潜水,一个士兵只会上弹。三个士兵是并能够相互替换的,因为除了自己的工作,其他的工作都不会做。如果对方持续攻击,那么士兵需要长时间的高强度的对抗,因为如果炮筒连续发射炮弹的速度比装弹速度快的话,上弹的士兵体力消耗非常大,时间一长就累倒了。这样一下子没有人会上弹了,这样就没法抵抗了。虽然有一个潜水的士兵,但是派不上用场。
    线程也如此,在服务器上,大量的并发的请求,如果只一个线程去处理,自然就处理不过来。如果只有一个线程能够执行这个处理任务,其他线程不会处理,只能看着服务器挂掉。我们这里说的是,一个线程执行一个线程函数,就决定了这个线程只能做一种任务了,即使线程没有死,也没有办法去做其他事情的。
    所以,既然线程池是为了让多个线程可以相互替代,一起完成同一个任务,自然,所有的线程都要会做同一件事情,这样在大量的请求到来时,多个线程一起上阵处理,这样就可以加快处理,可以提高服务器性能,原本一个线程要半个小时完成的任务,几个线程几分钟就完成了。这就是提高了性能。
    由此可知,多个线程完成同一个目标,也就要执行同一个线程函数咯。所以,我们必须指定这些线程的线程函数为同一个。好的,线程函数的指定我们分析完了。
    那么多个线程执行同一个线程函数,会不会有问题呢?在《线程池学习总结1:模拟实现线程池分析-创建线程池》文章中的例子我们提到了,多个线程是在工作间和休息间来回的切换的,没事就在休息间里呆着,有事情就去工作间干活。这是粗略的理解。而一个线程工作后,所有的工具等都要随身携带,否则会干扰其他线程工作。而且还要将工作台恢复成原样。因为大家都是做同样的事情,每一种状态都是工作中需要的,工作中会根据这些状态做处理。所以,一个线程做完后,不能让状态留存下来,这样会干扰其他线程的判断。这个概念叫做可重入。可重入是共享代码的基本特点,如果不能够可重入,代码就不能共享使用。我们多个线程共享一个线程函数,就必须符合可重入的特点。
    我用代码举例说明:
int sum=10;
void check()
{
    if(sum>0)sum--;
    else sum++;
}
    这个代码是不可重入的。可重入的意思就是,不管你什么时候执行代码,都和之前是否执行过没有任何关系,多个线程可以同时执行都不会相互影响。但是你可以看到,check操作的sum是全局变量,因为sum的值不同,线程的行为是不同的。所以一个线程将sum值加到超过零后,就会导致其他线程执行的时候是去减掉sum的值,反之则是导致其他线程去增加sum的值。这就使其他的线程在不同的时刻执行函数check会产生不同的效果。这就是被干扰了。可重入得到的效果是,不管什么时候执行,执行的效果是一致的。不可重入的代码被同时执行后,结果无法预料,更别说合作完成同一个目标了。更多可重入的分析,请阅读《什么是可重入函数与不可重入函数?可重入函数分析http://www.cjjjs.com/paper/gzsh/6262015850252.aspx》。
    如果将上述代码改为下面这样的,就是可重入的代码:
void check()
{
    int sum=10;
    if(sum>0)sum--;
    else sum++;
}
    这样不管什么时候执行check还是多个线程同时执行,没一个执行过程都是一样的,不受其他干扰。在线程池环境中,就好像每一个线程都是独立执行这个线程函数的。这样多个任务是分给多个线程执行,相互不受影响的独立完成了这个任务。
    然而就这样就完了吗?不是的。可重入代码是我们要考虑的一个问题。然而在另一方面我们还要考虑一个问题,那就是公共资源访问的问题。那么什么是公共资源访问问题呢?这个问题说的是,大家使用公共资源的时候,需要按照秩序使用。
否则会导致混乱。举个例子,假如公共厕所的卫生纸是公用的,大家每次使用都没有任何机制管理,任何人任何时候都可以使用,如果这样的话,假设一个人蹲完厕所,正准备拿纸的时候,被突如其来的一伙人拿完了,这下就不好了。哈哈,你可以想象这种场景。所以为了保证公共资源的合理正常的使用,在一个人使用的时候,其他人不能随意的使用,只有这个人用完了其他人才能使用。这样就可以保证蹲完厕所的人先用完其他人才可以拿到纸。
    公共资源访问的问题是因为多线程多任务的特性引起的。如果每一个线程执行任务直到执行完才换另一个线程执行的话,就不存在公共资源访问的问题。这就是单线程单任务的模式。DOS系统就是这样的模式。而Windows使用的是多任务的机制,这样会让多个线程不停的来回切换,让用户感觉多个线程都在同时执行。然而线程们可能在执行一半的时候,被请出CPU暂停运行,过一段时间再回到CPU继续执行。这样在离开了CPU后,之前访问的公共资源就可能被其他的线程使用,等再回来的时候物是人非了,会导致无法预料的结果。而且线程切换是无法预测的,程序员无法保证线程什么时候被执行的。所以,对于公共资源的访问,使用互斥机制。简单来说,我们可以用“锁”把我们正在使用的资源锁起来,即使我暂时撤离,别人也无法使用,我回来继续可以使用,只有我使用完了,我把锁打开,别人就可以随便使用了。
    所有线程共同完成任务一是要执行同样的线程函数,二是在状态方面,不能遗留状态而干扰其他线程的执行逻辑,三是公共资源需要互斥使用,也就是用锁锁住你还没有用完的资源,用完要释放。这三个问题如果不处理好,都会出问题,第一个会无法完成共同任务,无法提高性能,一盘散沙。第二个会导致每一个线程的执行结果各不相同,出现不可预料的结果,无法正确的完成任务,导致任务执行失败,甚至导致后续的任务全部失败,因为都会受到影响。第三个和第二个类似,不过更多的是逻辑上数据处理被其他线程破坏,无法达到预期效果。
    以上的几个点的详细分析,是做好线程池需要了解的,可能我并没有考虑到所有问题,暂时就将现在知道的全部分享出来,如果你有不错的经验,请告诉我,相互学习。