• linux网络编程之简单的线程池实现


    转眼间离15年的春节越来越近了,还有两周的工作时间貌似心已经不在异乡了,期待与家人团聚的日子,当然最后两周也得坚持站好最后一班岗,另外期待的日子往往是心里不能平静的,越是想着过年,反而日子过得越慢,于是乎,还是用学习来充斥这些碎片时间,当人一充实,时间也就过得快了,继续学习:

    上次中已经用互斥锁与条件变量来改造了生产者与消费者问题,这次利用它来实现一个线程池,加强对条件变量及互斥锁的认识,下面开始:

    关于什么是线程池,这里就不多说了,应该基本都在实际中用到过,下面关于线程池实现有几个点需要说明一下:

    线程池,顾名思义就是拥有若干个线程,对于线程的个数是有严格的要求的,并非越多越好,太多了会增加系统的开销,太少了又会降低并发量。

    执行时间较长的任务是不太适合放在线程池中进行处理,比如说:线程的执行时间跟进程的生命周期是一致的,那么这个任务的执行就没必要放到线程池中进行,直接用普通的线程既可。

    那线程池当中的线程个数究竟存放多少个比较合适呢?实际上这跟任务类型有关系:

    ①、计算密集型任务:一般这个任务是占用CPU的,它很少被外界的事件打断,这时线程个数 = CPU个数,如果线程个数>CPU个数,由于CPU的个数是一定的,那么能够并发的数目也是一定的,所以会用少量的CPU个数来调度多个线程,这肯定会涉及到线程与线程之间的切换开销,因而会降低效率。

    ②、I/O密集型任务:这种任务在运行时,可能会被I/O中断,也就是说这个线程会挂起,这时线程个数 > CPU个数

    那接下来先了解一下线程池实现中,需要用到的结构体:

    下面则开始实现,首先在头文件中定义上面的数据结构:

    其中用到了条件变量,这里对条件变量进行了简单的封装,所以先来看下是如何封装的:

    从声明的这些函数中很容易就能想到它们的各个实现:

    condition.c:

    #include "condition.h"
    
    //初使化条件变量,可想而知是对互斥锁和条件变量进行初始化
    int condition_init(condition_t *cond)
    {
        int status;
        if ((status = pthread_mutex_init(&cond->pmutex, NULL)))
            return status;
    
        if ((status = pthread_cond_init(&cond->pcond, NULL)))
            return status;
    
        return 0;
    }
    
    //对互斥锁进行锁定
    int condition_lock(condition_t *cond)
    {
        return pthread_mutex_lock(&cond->pmutex);
    }
    
    //对互斥锁进行解锁
    int condition_unlock(condition_t *cond)
    {
        return pthread_mutex_unlock(&cond->pmutex);
    }
    
    //在条件变量上等待条件
    int condition_wait(condition_t *cond)
    {
        return pthread_cond_wait(&cond->pcond, &cond->pmutex);
    }
    
    //具有超时功能的等待功能
    int condition_timedwait(condition_t *cond, const struct timespec *abstime)
    {
        return pthread_cond_timedwait(&cond->pcond, &cond->pmutex, abstime);
    }
    
    //向等待线程发起一个通知
    int condition_signal(condition_t *cond)
    {
        return pthread_cond_signal(&cond->pcond);
    }
    
    //向等待线程发起广播
    int condition_broadcast(condition_t* cond)
    {
        return pthread_cond_broadcast(&cond->pcond);
    }
    
    //销毁条件变量
    int condition_destroy(condition_t* cond)
    {
        int status;
        if ((status = pthread_mutex_destroy(&cond->pmutex)))
            return status;
    
        if ((status = pthread_cond_destroy(&cond->pcond)))
            return status;
    
        return 0;
    }

    接着来实现一下线程池:

    在正式实现这些函数之前,其实可以先从使用者的角度来看,如何使用这些线程池,如下:

    其实这是典型的“测试驱动开发”,先编写好测试代码,然后再来从使用的角度去具体实现,下面则开始具体实现线程池相应的方法:

    接下来实现往线程池中添加任务:

    其添加过程是从尾部进行添加的,其实就是单链表的应用。

    这里需要注意一个问题,就是在使用条件变量之前是需要对进行互斥的,因为队列资源是生产者与消费者都可以访问的,所以需要互斥:

    接下来来处理线程的执行入口函数,线程应该是等待任务并且处理任务,也就是它是一个消费者线程:

    下面来编译运行一下,在运行之后,需要在main函数中做一下sleep:

    而分析一下输出结果:

    而且是经过15秒之后,则进程退出了,但是有个问题,就是当任务执行完了,应该线程也能动态减少,目前当任务执行完了之后,所有线程都还在,也就是需要看到这样的输出:

    但是目前看不到这样的状态,而是等到进程退出来线程才销毁,所以需要对代码进行改进,这时就需要用到等待超时的一个函数:

    也就是如果线程等待超时了,则代表没有任务了,则该线程就可以销毁了,所以将condition_wait需要换成condition_timedwait函数:

    查看一相man帮助:

    下面来改造一下:

    【说明】:获取当前时间可以用函数clock_gettime:

    下面再来做超时处理:

    好了下面再来运行一下:

    从结果中可以看出:

    接下来就剩最后一个销毁方法没有实现了,而main中的sleep则可以去掉了:

    其中看到有等待条件变量,那是谁来通知该条件变量呢,当然是在任务执行时,于时需要修改任务执行线程里面的代码:

    下面再来编译运行一下,在运行之前,可以将之前的休眠代码去掉了:

    下面再来看下最终效果:

    可见当线程任务都执行完了,所有的线程也销毁了,这样代码就是比较合理的,好了今天的逻辑有些复杂,需好好消化。

  • 相关阅读:
    CSS复合选择器
    模块之shutil模块模块详解
    模块之sys模块详解
    模块之os模块详解
    map遍历
    java完美处理表情符
    死磕设计模式—建造者模式
    Java String getChars()方法
    Java知识系统回顾整理01基础06数组05复制数组
    Java知识系统回顾整理01基础06数组04增强型for循环
  • 原文地址:https://www.cnblogs.com/webor2006/p/4266726.html
Copyright © 2020-2023  润新知