Clamav杀毒软件源码分析笔记[六]

    Clamav杀毒软件源码分析笔记[六]

刺猬@http://blog.csdn.net/littlehedgehog

[线程处理]

我曾经说过Clamd强大的动力并不是来自于某些催情药的功用,而是内部提供的线程池功能功不可没.所谓线程池,我理解是暂时收容工作完成的线程,派发工作给无所事事的线程,这样可以让线程有暂时的不死之身,一直存在于内存之中.这样避免了频繁地创建线程,销毁线程.

先来看看所谓线程池的结构吧:

  1. /* 线程池结构 */typedef struct threadpool_tag{    pthread_mutex_t pool_mutex;     //池的锁结构 用于访问池时,可能涉及到对链表等操作,要保证互斥    pthread_cond_t pool_cond;    pthread_attr_t pool_attr;        pool_state_t state;             //池状态    int thr_max;                    //最大线程数量    int thr_alive;                  //活跃线程数量    int thr_idle;                   //空闲线程数量    int idle_timeout;               //空闲时间        void (*handler)(void *);        //这个是线程所用的函数        work_queue_t *queue;            //工作队列} threadpool_t;

这个结构普通至极,用旭哥的话来说那就是”那个简单──“.如果用C++来描述这个结构,或许改称呼”类”,还应该有所谓的…..那个….成员函数, 可惜我C++实在不好,旁边有位C++高手,此时此刻正研究3D引擎,不便打扰.

线程池创建:

  1. /* 创建线程池 注意这里的参数handler是指线程工作函数!* 瞅瞅函数指针的定义吧,这个很容易搞错的 void (*handler)(void *); 定义一个函数指针,这个函数是个(无返回值)、(参数为void *类型的指针) */threadpool_t *thrmgr_new(int max_threads, int idle_timeout, void (*handler)(void *)){    threadpool_t *threadpool;    if (max_threads 0)    {        return NULL;    }    threadpool = (threadpool_t *) mmalloc(sizeof(threadpool_t));    threadpool->queue = work_queue_new();       //初始化线程队列    if (!threadpool->queue)    {        free(threadpool);        return NULL;    }       threadpool->thr_max = max_threads;    threadpool->thr_alive = 0;    threadpool->thr_idle = 0;    threadpool->idle_timeout = idle_timeout;    threadpool->handler = handler;    /* 当代码使用 malloc() 分配一个新的互斥对象时,使用下面这种动态init方法。此时,静态初始化方法是行不通的 */    /* 参考资料在IBM里: http://www.ibm.com/developerworks/cn/linux/thread/posix_thread1/index.html */    pthread_mutex_init(&(threadpool->pool_mutex), NULL);    if (pthread_cond_init(&(threadpool->pool_cond), NULL) != 0)     {        free(threadpool);        return NULL;    }    if (pthread_attr_init(&(threadpool->pool_attr)) != 0)     {        free(threadpool);        return NULL;    }    if (pthread_attr_setdetachstate(&(threadpool->pool_attr), PTHREAD_CREATE_DETACHED) != 0) //设置线程分离属性    {        free(threadpool);        return NULL;    }    threadpool->state = POOL_VALID;    return threadpool;}

线程池创建绝非难事,就是一系列结构成员复制而已,如果对线程了解不深,如我一般,可以看看我注释中提供的IBM资料

线程池销毁,我说”销毁”这个词显得太学究,如果说”干掉线程池”有太过于黑社会化. 销毁跟创建大致相反,我只是说了大致而已,注意,因为我们要考虑如果销毁线程的时候,还有线程运行的话,我们必须等待.

  1. void thrmgr_destroy(threadpool_t *threadpool){    if (!threadpool || (threadpool->state != POOL_VALID)) {        return;    }    if (pthread_mutex_lock(&threadpool->pool_mutex) != 0) {        logg(“!Mutex lock failed/n”);        exit(-1);    }    threadpool->state = POOL_EXIT;    /* wait for threads to exit */    if (threadpool->thr_alive > 0) {        if (pthread_cond_broadcast(&(threadpool->pool_cond)) != 0) {            pthread_mutex_unlock(&threadpool->pool_mutex);            return;        }    }    /* 当活跃线程数大于0时,我们需要等待,等待线程结束*/    while (threadpool->thr_alive > 0)     {        /* 线程完成工作前都要激活条件变量,然后这里被信号惊醒,然后检查如果还是大于0,继续睡觉. 这就是while的作用*/        if (pthread_cond_wait (&threadpool->pool_cond, &threadpool->pool_mutex) != 0)           {               pthread_mutex_unlock(&threadpool->pool_mutex);            return;        }    }    if (pthread_mutex_unlock(&threadpool->pool_mutex) != 0) {        logg(“!Mutex unlock failed/n”);        exit(-1);    }    /* 这里必须要destroy, 并不是为了显示我们专业,而是new一个线程池时我们用的init,出来混,迟早要还的*/    pthread_mutex_destroy(&(threadpool->pool_mutex));    pthread_cond_destroy(&(threadpool->pool_cond));    pthread_attr_destroy(&(threadpool->pool_attr));    free(threadpool);    return;}

分配线程工作

  1. /* thread manager dispatch a thread to do something——当经理接到客户的要求时,他就dispatch(派遣)一个thread去干工作啦!* 要注意的是经理下面有一大堆thread家伙在无所事事,所以我们不需要创建thread,直接安排工作就是了。这就是线程池的好处。*/int thrmgr_dispatch(threadpool_t *threadpool, void *user_data){    pthread_t thr_id;    if (!threadpool)     {        return FALSE;    }    /* Lock the threadpool */    /* 为什么要锁住为我们要对链表队列进行操作,为了防止多线程和SMP的打扰 */    if (pthread_mutex_lock(&(threadpool->pool_mutex)) != 0)     {        logg(“!Mutex lock failed/n”);        return FALSE;    }    if (threadpool->state != POOL_VALID)    {        if (pthread_mutex_unlock(&(threadpool->pool_mutex)) != 0)         {            logg(“!Mutex unlock failed/n”);            return FALSE;        }        return FALSE;    }    work_queue_add(threadpool->queue, user_data);    /* manager下面的弟兄也是慢慢慢慢招募来的,所以不可能一来就有线程,最开始我们还是得培养… */    if ((threadpool->thr_idle == 0) && (threadpool->thr_alive <  class="hljs-punctuation">->thr_max))       //如果没有空闲线程(有可能线程池有不少线程,但是它们都有活儿干,所以不便打扰),并且线程数没有超出最大值    {        /* Start a new thread */        if (pthread_create(&thr_id, &(threadpool->pool_attr),thrmgr_worker, threadpool) != 0)         {            logg(“!pthread_create failed/n”);        }         else        {            threadpool->thr_alive++;        }    }    /*这里是经理发信号,表明了有任务了,这里会唤醒其他等待线程,直接受影响的代码是thrmgr_worker中的while循环。     *线程条件我还得好好研究下kernel代码,不过今天是说应用,就此打住 */    pthread_cond_signal(&(threadpool->pool_cond));          if (pthread_mutex_unlock(&(threadpool->pool_mutex)) != 0)     {        logg(“!Mutex unlock failed/n”);        return FALSE;    }    return TRUE;}

分配线程工作,大致两步 1.把工作加入队列当中,  2. 通知线程有工作了,这里用线程条件变量


线程接到信号后就会去工作了:

  1. /* 读这个函数的时候尤其要注意线程互斥和线程条件等待  ——因为我也被骗了*//* 这里附上关于有关条件等待的资料 如果你对线程也不够自信 Please read it carefully  等待条件有两种方式:无条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait())的竞争条件(Race Condition)。 mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态[这句话比较拗口,就是说线程在指向wait之前必须那个锁必须要锁上],并在线程挂起进入等待队列前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。[这里加锁是为了获取资源,仅仅为对应*/void *thrmgr_worker(void *arg){    threadpool_t *threadpool = (threadpool_t *) arg;    void *job_data;    int retval, must_exit = FALSE;    struct timespec timeout;    /* loop looking for work 注意这里是死循环,意味着只能是出了意外 比如线程池强烈要求结束*/    for (;;)    {        if (pthread_mutex_lock(&(threadpool->pool_mutex)) != 0)     //锁住,一方面要设置线程池(防止条件竞争),一方面为下面的wait condition做准备        {            /* Fatal error */            logg(“!Fatal: mutex lock failed/n”);            exit(-2);        }        timeout.tv_sec = time(NULL) + threadpool->idle_timeout;         //这里的time(NULL)  不要被这种用法蒙蔽  同样是返回从1970.1.1 0:00 到现在的秒数        timeout.tv_nsec = 0;        threadpool->thr_idle++;     //为什么这里要加一nbsp;表明当前线程在等待,既然在等待,必定是空闲的        while (((job_data=work_queue_pop(threadpool->queue)) == NULL)&& (threadpool->state != POOL_EXIT))        {            /* Sleep, awaiting wakeup */            retval = pthread_cond_timedwait(&(threadpool->pool_cond),&(threadpool->pool_mutex), &timeout); //进入等待时候要释放锁!退出等待时候会加锁!            if (retval == ETIMEDOUT)        //返回要么是等待成功了(有任务进来了),或者是超时,这里是处理超时            {                must_exit = TRUE;                break;                                  }        }        threadpool->thr_idle–;         //这里从while跳出来后 要么是有任务了,要么超时,所以减一        if (threadpool->state == POOL_EXIT)         {            must_exit = TRUE;        }        if (pthread_mutex_unlock(&(threadpool->pool_mutex)) != 0)       //这里释放锁不是因为pthread_cond_timedwait前我们加锁了!! 如果还是这样认为请再阅读函数前解释        {            /* Fatal error */            logg(“!Fatal: mutex unlock failed/n”);            exit(-2);        }        if (job_data)               //有工作那就做         {            threadpool->handler(job_data);        }         else if (must_exit)         //如果线程池的状态标识为POOL_EXIT 或者超时    表明该是要退出的时候了        {            break;        }    }    if (pthread_mutex_lock(&(threadpool->pool_mutex)) != 0)         //这里又尝试加锁 主要是下面我们又要对线程池操作 凡是涉及到对线程池操作的都要考虑加锁问题    {        /* Fatal error */        logg(“!Fatal: mutex lock failed/n”);        exit(-2);    }    threadpool->thr_alive–;    if (threadpool->thr_alive == 0)     {        /* signal that all threads are finished */        pthread_cond_broadcast(&threadpool->pool_cond);         //如果线程更改某些共享数据,而且它想要唤醒所有正在等待的线程,则应使用 pthread_cond_broadcast 调用    }    if (pthread_mutex_unlock(&(threadpool->pool_mutex)) != 0)     {        /* Fatal error */        logg(“!Fatal: mutex unlock failed/n”);        exit(-2);    }    return NULL;}

这个函数是每个线程工作函数,但其实真正在干事的只有 threadpool->handler(job_data);其它的都是做一些判断工作,比如有没有job要做啊等等. 注意这里的for死循环,就是它把线程一直困在了内存中,不然线程函数早就返回了,系统也就回收了这个线程. 这个死循环并不会太耗系统资源,因为线程大部分时间要么在睡眠,要么在工作.不会有忙循环的情况.


来源:littlehedgehog

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2008年9月6日
下一篇 2008年9月6日

相关推荐