Clamav杀毒软件源码分析笔记[六]
刺猬@http://blog.csdn.net/littlehedgehog
[线程处理]
我曾经说过Clamd强大的动力并不是来自于某些催情药的功用,而是内部提供的线程池功能功不可没.所谓线程池,我理解是暂时收容工作完成的线程,派发工作给无所事事的线程,这样可以让线程有暂时的不死之身,一直存在于内存之中.这样避免了频繁地创建线程,销毁线程.
先来看看所谓线程池的结构吧:
- /* 线程池结构 */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引擎,不便打扰.
线程池创建:
- /* 创建线程池 注意这里的参数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资料
线程池销毁,我说”销毁”这个词显得太学究,如果说”干掉线程池”有太过于黑 会化. 销毁跟创建大致相反,我只是说了大致而已,注意,因为我们要考虑如果销毁线程的时候,还有线程运行的话,我们必须等待.
- 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;}
分配线程工作
- /* 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. 通知线程有工作了,这里用线程条件变量
线程接到信 后就会去工作了:
- /* 读这个函数的时候尤其要注意线程互斥和线程条件等待 ——因为我也被骗了*//* 这里附上关于有关条件等待的资料 如果你对线程也不够自信 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死循环,就是它把线程一直困在了内存中,不然线程函数早就返回了,系统也就回收了这个线程. 这个死循环并不会太耗系统资源,因为线程大部分时间要么在睡眠,要么在工作.不会有忙循环的情况.
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!