前文我已经通过理发师的故事讲述了服务器程序的设计及其演变过程,不知道各位看客是否能理解其中的奥妙。本问将着重介绍线程池和工作队列的相关问题,其后还将给出自己设计的api及源码。
线程池(Thread Pool)这又是一个能顾名思义的名字(好的事物都是如此),既然我们大家都清楚鱼池,那么肯定也就能理解线程池就是用来盛放线程(不要告诉我:你不知道什么是线程哦)的。
对于一个事物,如果不知道它产生的背景似乎是不妥的,因为那样你将不能很好的理解他和灵活地应用它。在理发师的故事中,理发师为了缩短把学徒从后厅叫到前厅的时间,规定了在前台等待的学徒的数目。这映射到服务器上就变成了规定预先创建的线程数,
缩短线程创建的时间从而加快了系统响应速度,
减少线程创建和销毁的开销。由于
服务器要预先创建一定数目的线程,所以这个技术,也叫作
线程预创建技术。
最简单的线程池中的线程数目是固定的,每当一个任务来临时,都会找一个目前空闲的线程来处理这个任务,如果没有空闲线程可用,那么将报错;处理任务的线程做完任务后并不是马上退出而是将自己标示成空闲,然后休眠等待新任务。由于服务器的负载在时间上并不是平均分配的,如果存在大量的空闲线程,对服务器资源也是一种浪费,所以现实中的线程池实现都支持
动态调整线程数。一个典型的设计可以分别设置所有线程数和空闲线程数的最大和最小值,当空闲线程比较小的时候,服务器会增加线程,反之亦然。对于增加线程的时机,主要有两个:分配任务时和任务结束时,因为在分配任务时增加线程会增加服务器的响应时间,所以在我下面的实现中采用的是在任务结束时增加线程。
线程池还有一个附加的作用,它能够
控制机器上的并发任务数,使其不超过某个极限值,也算是一种安全增强。
工作队列就是存放工作(任务)的队列,其中的“队列”也道出了任务先进先出(FIFO)的性质。
有人倾向于把工作队列作为线程池的一部分来实现,个人认为这是不恰当的。因为工作队列在任务的响应时间上没有保证,而线程池则提供当前有没有空闲线程可以处理这个任务的信息,两者在语意上完全不同。但是,没有保证也并不总是坏事,对于超载的请求,它用
延迟处理代替了拒绝服务,
平滑了迸发请求对服务器的冲击,这种
缓冲机制对于一些对时间不是很敏感地且服务时间较短的服务(HTTP服务就是此类)还是很适合的。
API我所设计的C语言API如下,比较简单明了,相信不用我再解释什么了。
XSThreadPool* xs_thread_pool_create(int min, int max, int idle_min,
int idle_max);
int xs_thread_pool_do(XSThreadPool *tp, xs_thread_func_t func, void *args);
int xs_thread_pool_destroy(XSThreadPool *tp);
|
XSWorkQueue* xs_work_queue_create(int max, int tp_min, int tp_max,
int tp_idle_min, int tp_idle_max);
int xs_work_queue_enqueue(XSWorkQueue *q, xs_thread_func_t func,
xs_thread_func_t cleanup, void *args);
int xs_work_queue_destroy(XSWorkQueue *q);
|
关于xs_这个前缀,是我网名xiaosuo的缩写,实在想不出什么好名字。
有必要提醒一下:任务之间不要再用pthread_cancel来同步,不要用pthread_exit来退出,不然后果自负。
后记在Linux下用线程搞开发,确实比较累,稍不留心就有可能造成死锁,希望此类事情不要在上述代码中再次发生,调试过程中因为pthread_mutex_lock不是取消点(cancel point)所导致的死锁已经让我焦头烂额了。
源码下载
阅读(910) | 评论(0) | 转发(0) |