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

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


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





[线程处理]


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



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


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

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


线程池创建:


  1. /* 创建线程池 注意这里的参数handler是指线程工作函数!
  2. * 瞅瞅函数指针的定义吧,这个很容易搞错的 void (*handler)(void *); 定义一个函数指针,这个函数是个(无返回值)、(参数为void *类型的指针) */
  3. threadpool_t *thrmgr_new(int max_threads, int idle_timeout, void (*handler)(void *))
  4. {
  5.     threadpool_t *threadpool;
  6.     if (max_threads <= 0)
  7.     {
  8.         return NULL;
  9.     }
  10.     threadpool = (threadpool_t *) mmalloc(sizeof(threadpool_t));
  11.     threadpool->queue = work_queue_new();       //初始化线程队列
  12.     if (!threadpool->queue)
  13.     {
  14.         free(threadpool);
  15.         return NULL;
  16.     }   
  17.     threadpool->thr_max = max_threads;
  18.     threadpool->thr_alive = 0;
  19.     threadpool->thr_idle = 0;
  20.     threadpool->idle_timeout = idle_timeout;
  21.     threadpool->handler = handler;
  22.     /* 当代码使用 malloc() 分配一个新的互斥对象时,使用下面这种动态init方法。此时,静态初始化方法是行不通的 */
  23.     /* 参考资料在IBM里: http://www.ibm.com/developerworks/cn/linux/thread/posix_thread1/index.html */
  24.     pthread_mutex_init(&(threadpool->pool_mutex), NULL);
  25.     if (pthread_cond_init(&(threadpool->pool_cond), NULL) != 0) 
  26.     {
  27.         free(threadpool);
  28.         return NULL;
  29.     }
  30.     if (pthread_attr_init(&(threadpool->pool_attr)) != 0) 
  31.     {
  32.         free(threadpool);
  33.         return NULL;
  34.     }
  35.     if (pthread_attr_setdetachstate(&(threadpool->pool_attr), PTHREAD_CREATE_DETACHED) != 0) //设置线程分离属性
  36.     {
  37.         free(threadpool);
  38.         return NULL;
  39.     }
  40.     threadpool->state = POOL_VALID;
  41.     return threadpool;
  42. }



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


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


  1. void thrmgr_destroy(threadpool_t *threadpool)
  2. {
  3.     if (!threadpool || (threadpool->state != POOL_VALID)) {
  4.         return;
  5.     }
  6.     if (pthread_mutex_lock(&threadpool->pool_mutex) != 0) {
  7.         logg("!Mutex lock failed/n");
  8.         exit(-1);
  9.     }
  10.     threadpool->state = POOL_EXIT;
  11.     /* wait for threads to exit */
  12.     if (threadpool->thr_alive > 0) {
  13.         if (pthread_cond_broadcast(&(threadpool->pool_cond)) != 0) {
  14.             pthread_mutex_unlock(&threadpool->pool_mutex);
  15.             return;
  16.         }
  17.     }
  18.     /* 当活跃线程数大于0时,我们需要等待,等待线程结束*/
  19.     while (threadpool->thr_alive > 0) 
  20.     {
  21.         /* 线程完成工作前都要激活条件变量,然后这里被信号惊醒,然后检查如果还是大于0,继续睡觉. 这就是while的作用*/
  22.         if (pthread_cond_wait (&threadpool->pool_cond, &threadpool->pool_mutex) != 0)   
  23.         {   
  24.             pthread_mutex_unlock(&threadpool->pool_mutex);
  25.             return;
  26.         }
  27.     }
  28.     if (pthread_mutex_unlock(&threadpool->pool_mutex) != 0) {
  29.         logg("!Mutex unlock failed/n");
  30.         exit(-1);
  31.     }
  32.     /* 这里必须要destroy, 并不是为了显示我们专业,而是new一个线程池时我们用的init,出来混,迟早要还的*/
  33.     pthread_mutex_destroy(&(threadpool->pool_mutex));
  34.     pthread_cond_destroy(&(threadpool->pool_cond));
  35.     pthread_attr_destroy(&(threadpool->pool_attr));
  36.     free(threadpool);
  37.     return;
  38. }

分配线程工作


  1. /* thread manager dispatch a thread to do something——当经理接到客户的要求时,他就dispatch(派遣)一个thread去干工作啦!
  2. * 要注意的是经理下面有一大堆thread家伙在无所事事,所以我们不需要创建thread,直接安排工作就是了。这就是线程池的好处。*/
  3. int thrmgr_dispatch(threadpool_t *threadpool, void *user_data)
  4. {
  5.     pthread_t thr_id;
  6.     if (!threadpool) 
  7.     {
  8.         return FALSE;
  9.     }
  10.     /* Lock the threadpool */
  11.     /* 为什么要锁住?因为我们要对链表队列进行操作,为了防止多线程和SMP的打扰 */
  12.     if (pthread_mutex_lock(&(threadpool->pool_mutex)) != 0) 
  13.     {
  14.         logg("!Mutex lock failed/n");
  15.         return FALSE;
  16.     }
  17.     if (threadpool->state != POOL_VALID)
  18.     {
  19.         if (pthread_mutex_unlock(&(threadpool->pool_mutex)) != 0) 
  20.         {
  21.             logg("!Mutex unlock failed/n");
  22.             return FALSE;
  23.         }
  24.         return FALSE;
  25.     }
  26.     work_queue_add(threadpool->queue, user_data);
  27.     /* manager下面的弟兄也是慢慢慢慢招募来的,所以不可能一来就有线程,最开始我们还是得培养... */
  28.     if ((threadpool->thr_idle == 0) && (threadpool->thr_alive < threadpool->thr_max))       //如果没有空闲线程(有可能线程池有不少线程,但是它们都有活儿干,所以不便打扰),并且线程数没有超出最大值
  29.     {
  30.         /* Start a new thread */
  31.         if (pthread_create(&thr_id, &(threadpool->pool_attr),thrmgr_worker, threadpool) != 0) 
  32.         {
  33.             logg("!pthread_create failed/n");
  34.         } 
  35.         else
  36.         {
  37.             threadpool->thr_alive++;
  38.         }
  39.     }
  40.     /*这里是经理发信号,表明了有任务了,这里会唤醒其他等待线程,直接受影响的代码是thrmgr_worker中的while循环。
  41.      *线程条件我还得好好研究下kernel代码,不过今天是说应用,就此打住 */
  42.     pthread_cond_signal(&(threadpool->pool_cond));      
  43.     if (pthread_mutex_unlock(&(threadpool->pool_mutex)) != 0) 
  44.     {
  45.         logg("!Mutex unlock failed/n");
  46.         return FALSE;
  47.     }
  48.     return TRUE;
  49. }

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




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

  1. /* 读这个函数的时候尤其要注意线程互斥和线程条件等待  ——因为我也被骗了*/
  2. /* 这里附上关于有关条件等待的资料 如果你对线程也不够自信 Please read it carefully 
  3.  等待条件有两种方式:无条件等待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()前的加锁动作对应。[这里加锁是为了获取资源,仅仅为对应?]*/
  4. void *thrmgr_worker(void *arg)
  5. {
  6.     threadpool_t *threadpool = (threadpool_t *) arg;
  7.     void *job_data;
  8.     int retval, must_exit = FALSE;
  9.     struct timespec timeout;
  10.     /* loop looking for work 注意这里是死循环,意味着只能是出了意外 比如线程池强烈要求结束*/
  11.     for (;;)
  12.     {
  13.         if (pthread_mutex_lock(&(threadpool->pool_mutex)) != 0)     //锁住,一方面要设置线程池(防止条件竞争),一方面为下面的wait condition做准备
  14.         {
  15.             /* Fatal error */
  16.             logg("!Fatal: mutex lock failed/n");
  17.             exit(-2);
  18.         }
  19.         timeout.tv_sec = time(NULL) + threadpool->idle_timeout;         //这里的time(NULL)  不要被这种用法蒙蔽  同样是返回从1970.1.1 0:00 到现在的秒数
  20.         timeout.tv_nsec = 0;
  21.         threadpool->thr_idle++;     //为什么这里要加一? 表明当前线程在等待,既然在等待,必定是空闲的
  22.         while (((job_data=work_queue_pop(threadpool->queue)) == NULL)&& (threadpool->state != POOL_EXIT))
  23.         {
  24.             /* Sleep, awaiting wakeup */
  25.             retval = pthread_cond_timedwait(&(threadpool->pool_cond),&(threadpool->pool_mutex), &timeout); //进入等待时候要释放锁!退出等待时候会加锁!
  26.             if (retval == ETIMEDOUT)        //返回要么是等待成功了(有任务进来了),或者是超时,这里是处理超时
  27.             {
  28.                 must_exit = TRUE;
  29.                 break;                      
  30.             }
  31.         }
  32.         threadpool->thr_idle--;         //这里从while跳出来后 要么是有任务了,要么超时,所以减一
  33.         if (threadpool->state == POOL_EXIT) 
  34.         {
  35.             must_exit = TRUE;
  36.         }
  37.         if (pthread_mutex_unlock(&(threadpool->pool_mutex)) != 0)       //这里释放锁不是因为pthread_cond_timedwait前我们加锁了!! 如果还是这样认为请再阅读函数前解释
  38.         {
  39.             /* Fatal error */
  40.             logg("!Fatal: mutex unlock failed/n");
  41.             exit(-2);
  42.         }
  43.         if (job_data)               //有工作那就做 
  44.         {
  45.             threadpool->handler(job_data);
  46.         } 
  47.         else if (must_exit)         //如果线程池的状态标识为POOL_EXIT 或者超时    表明该是要退出的时候了
  48.         {
  49.             break;
  50.         }
  51.     }
  52.     if (pthread_mutex_lock(&(threadpool->pool_mutex)) != 0)         //这里又尝试加锁 主要是下面我们又要对线程池操作 凡是涉及到对线程池操作的都要考虑加锁问题
  53.     {
  54.         /* Fatal error */
  55.         logg("!Fatal: mutex lock failed/n");
  56.         exit(-2);
  57.     }
  58.     threadpool->thr_alive--;
  59.     if (threadpool->thr_alive == 0) 
  60.     {
  61.         /* signal that all threads are finished */
  62.         pthread_cond_broadcast(&threadpool->pool_cond);         //如果线程更改某些共享数据,而且它想要唤醒所有正在等待的线程,则应使用 pthread_cond_broadcast 调用
  63.     }
  64.     if (pthread_mutex_unlock(&(threadpool->pool_mutex)) != 0) 
  65.     {
  66.         /* Fatal error */
  67.         logg("!Fatal: mutex unlock failed/n");
  68.         exit(-2);
  69.     }
  70.     return NULL;
  71. }



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









你可能感兴趣的:(thread,工作,IBM,null,任务,杀毒软件)