WiFiDog项目是热门的针对captive portal 的开源解决方案(),而ApFree则针对WiFiDog原有的功能进行了些扩展并进行了性能方面的优化()。
线程池技术的应用就是ApFree针对WiFiDog在并行处理性能的改善。
在传统的多线程服务器模型中,一旦有个请求到达,就创建一个新的线程,由该线程执行任务,任务执行完毕之后,线程就退出,这就是"即时创建,即时销毁"的策略。虽然线程的创建和销毁相比进程的创建和销毁是轻量级的,但是当我们的任务需要进行大量线程的创建和销毁操作,或者线程任务的执行时间较短但次数较频繁,那么这些用于创建销毁线程的消耗就会变成的相当大。这个时候,线程池就可以很好的缓解系统的压力。
线程池的好处就在于线程复用,一个任务处理完成后,当前线程可以直接处理下一个任务,而不是销毁后再创建,非常适用于连续产生大量并发任务的场合。
ApFree里线程池相关的数据结构和API:
struct threadpool_t { pthread_mutex_t lock; pthread_cond_t notify; pthread_t *threads; threadpool_task_t *queue; int thread_count; int queue_size; int head; int tail; int count; int shutdown; int started;};
threadpool_t *threadpool_create(int thread_count, int queue_size, int flags);int threadpool_add(threadpool_t *pool, void (*routine)(void *), void *arg, int flags);int threadpool_destroy(threadpool_t *pool, int flags);
在 struct threadpool_t 中:
- 互斥锁lock和条件变量notify来实现对于线程池的互斥和唤醒操作
- threads 存储线程池中线程的实例
- 任务队列queue存储线程执行任务的回调函数和参数
- head、tail、count成员用于管理queue任务队列
- shutdown、started成员用于表达线程池的状态
工作原理
- threadpool_create创建指定个数的线程,相应线程通过pthread_cond_wait睡眠在条件变量notify上。
- threadpool_add向任务队列queue中追加新的任务回调函数和参数,并通pthread_cond_signal唤醒等待在条件变量notify上的线程,继而进行任务的执行。
相关思考
- 关于queue处于满的情况处理。 在ApFree的应用场景中由于并发数相对较少,并且对于客户连接的处理也相对简单,并不太会出现queue满的情况,所以ApFree中就做了简化处理:
if (pool->count == pool->queue_size) { err = threadpool_queue_full; break; }
这样在用http_load测试时,一旦queue处于满状态就会出现错误 "Connection refused"。
针对一般的应用场景,我们可以采用以下几种方案作为对策:
- 增加关于queue队列状态的条件变量,一旦queue队列为满,那么接受客户连接的线程(主线程)就wait在这个条件变量上。这可能会导致在queue队列满的状况下,并发处理性能的急剧下降。
- 在queue队列满的情况下,一旦有新的任务被追加就通过创建新的线程来解决。这样内存的开销取决于最大的并发连接数,可能导致不必要的内存开销。
- 基于方案2,可以创建线程池管理线程来监控线程池的运行负载来动态调整线程池中线程数量。