nginx是采用多进程模型,master和worker之间主要通过pipe管道的方式进行通信,多进程的优势就在于各个进程互不影响。其实,nginx代码中也提供了一个thread_pool(线程池)的核心模块来处理多任务的。主要放在ngx_thread_pool.c文件中。
线程池的数据结构
task结构,为单链表组成任务队列,主要成员是event事件和handler回调方法
id每次push进一个任务,pool的全局静态id会自增并分配
//在中添加到了ngx_thread_pool_s->queue队列中,也就是添加到ngx_thread_pool_s对应的线程池队列中
struct ngx_thread_task_s {
ngx_thread_task_t *next; //指向下一个提交的任务
ngx_uint_t id; //任务id 没添加一个任务就自增加,见ngx_thread_pool_task_id
void *ctx; //执行回调函数的参数
//ngx_thread_pool_cycle中执行
void (*handler)(void *data, ngx_log_t *log); //回调函数 执行完handler后会通过ngx_notify执行event->handler
//执行完handler后会通过ngx_notify执行event->handler
ngx_event_t event; //一个任务和一个事件对应 事件在通过ngx_notify在ngx_thread_pool_handler中执行
};
threadpool结构。主要是任务队列,互斥锁,条件变量,线程个数,任务队列主要有头指针和尾指针的地址
typedef struct {
ngx_array_t pools;
} ngx_thread_pool_conf_t;
typedef struct { //见ngx_thread_pool_done
ngx_thread_task_t *first;
/*
*ngx_thread_pool_t->queue.last = task; 新添加的任务通过last连接在一起
ngx_thread_pool_t->queue.last = &task->next; 下次在添加新任务就让task->next指向新任务了
*/
ngx_thread_task_t **last;
} ngx_thread_pool_queue_t; //线程池队列 初始化在ngx_thread_pool_queue_init
#define ngx_thread_pool_queue_init(q) \
(q)->first = NULL; \
(q)->last = &(q)->first
//一个该结构对应一个threads_pool配置
struct ngx_thread_pool_s {//该结构式存放在ngx_thread_pool_conf_t->pool数组中的,见ngx_thread_pool_init_worker
ngx_thread_mutex_t mtx; //线程锁 ngx_thread_pool_init中初始化
//ngx_thread_task_post中添加的任务被添加到该队列中
ngx_thread_pool_queue_t queue;//ngx_thread_pool_init ngx_thread_pool_queue_init中初始化
//在该线程池poll中每添加一个线程,waiting子减,当线程全部正在执行任务后,waiting会恢复到0
//如果所有线程都已经在执行任务(也就是waiting>-0),又来了任务,那么任务就只能等待。所以waiting表示等待被执行的任务数
ngx_int_t waiting;//等待的任务数 ngx_thread_task_post加1 ngx_thread_pool_cycle减1
ngx_thread_cond_t cond;//条件变量 ngx_thread_pool_init中初始化
ngx_log_t *log;//ngx_thread_pool_init中初始化
ngx_str_t name;//thread_pool name threads=number [max_queue=number];中的name ngx_thread_pool
//如果没有配置,在ngx_thread_pool_init_conf默认赋值为32
ngx_uint_t threads;//thread_pool name threads=number [max_queue=number];中的number ngx_thread_pool
//如果没有配置,在ngx_thread_pool_init_conf默认赋值为65535
//指的是线程已经全部用完的情况下,还可以添加多少个任务到等待队列
ngx_int_t max_queue;//thread_pool name threads=number [max_queue=number];中的max_queue ngx_thread_pool
u_char *file;//配置文件名
ngx_uint_t line;//thread_pool配置在配置文件中的行号
};
//创建线程池所需的基础结构
static ngx_int_t
ngx_thread_pool_init_worker(ngx_cycle_t *cycle)
{
ngx_uint_t i;
ngx_thread_pool_t **tpp;
ngx_thread_pool_conf_t *tcf;
//如果不是worker或者只有一个worker就不起用线程池
if (ngx_process != NGX_PROCESS_WORKER
&& ngx_process != NGX_PROCESS_SINGLE)
{
return NGX_OK;
}
//初始化任务队列
ngx_thread_pool_queue_init(&ngx_thread_pool_done);
tpp = tcf->pools.elts;
for (i = 0; i < tcf->pools.nelts; i++) {
//初始化各个线程池
if (ngx_thread_pool_init(tpp[i], cycle->log, cycle->pool) != NGX_OK) {
return NGX_ERROR;
}
}
return NGX_OK;
}
线程池的初始化:初始化互斥锁,条件变量,创建每个线程
static ngx_int_t
ngx_thread_pool_init(ngx_thread_pool_t *tp, ngx_log_t *log, ngx_pool_t *pool)
{
int err;
pthread_t tid;
ngx_uint_t n;
pthread_attr_t attr;
if (ngx_notify == NULL) {
ngx_log_error(NGX_LOG_ALERT, log, 0,
"the configured event method cannot be used with thread pools");
return NGX_ERROR;
}
ngx_thread_pool_queue_init(&tp->queue);
if (ngx_thread_mutex_create(&tp->mtx, log) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_thread_cond_create(&tp->cond, log) != NGX_OK) {
(void) ngx_thread_mutex_destroy(&tp->mtx, log);
return NGX_ERROR;
}
tp->log = log;
err = pthread_attr_init(&attr);
if (err) {
ngx_log_error(NGX_LOG_ALERT, log, err,
"pthread_attr_init() failed");
return NGX_ERROR;
}
err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (err) {
ngx_log_error(NGX_LOG_ALERT, log, err,
"pthread_attr_setdetachstate() failed");
return NGX_ERROR;
}
#if 0
err = pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN);
if (err) {
ngx_log_error(NGX_LOG_ALERT, log, err,
"pthread_attr_setstacksize() failed");
return NGX_ERROR;
}
#endif
for (n = 0; n < tp->threads; n++) {
err = pthread_create(&tid, &attr, ngx_thread_pool_cycle, tp);
if (err) {
ngx_log_error(NGX_LOG_ALERT, log, err,
"pthread_create() failed");
return NGX_ERROR;
}
}
(void) pthread_attr_destroy(&attr);
return NGX_OK;
}
每个线程创建后执行的函数,data参数传进来的就是线程池,主要执行一个循环,每次循环取出等待的任务队列上的任务,如果没有阻塞等待条件满足了唤醒,之后调用task的handler方法之后会用自旋锁保护done队列,ngx_thread_pool_done队列是调用过handler进入线程处理的队列,每次把task执行完handler后push进队列中,然后唤醒ngx_notify(ngx_thread_pool_handler)方法, 正式进入事件的处理,ngx_memory_barrier()是为了//防止编译器优化,保证解锁操作是在上述语句执行完毕后再去执行的
。
static void *
ngx_thread_pool_cycle(void *data)
{
ngx_thread_pool_t *tp = data;
int err;
sigset_t set;
ngx_thread_task_t *task;
#if 0
ngx_time_update();
#endif
ngx_log_debug1(NGX_LOG_DEBUG_CORE, tp->log, 0,
"thread in pool \"%V\" started", &tp->name);
sigfillset(&set);
sigdelset(&set, SIGILL);
sigdelset(&set, SIGFPE);
sigdelset(&set, SIGSEGV);
sigdelset(&set, SIGBUS);
err = pthread_sigmask(SIG_BLOCK, &set, NULL);
if (err) {
ngx_log_error(NGX_LOG_ALERT, tp->log, err, "pthread_sigmask() failed");
return NULL;
}
for ( ;; ) {
if (ngx_thread_mutex_lock(&tp->mtx, tp->log) != NGX_OK) {
return NULL;
}
/* the number may become negative */
tp->waiting--;
while (tp->queue.first == NULL) {
if (ngx_thread_cond_wait(&tp->cond, &tp->mtx, tp->log)
!= NGX_OK)
{
//异常情况捕获释放锁后返回
(void) ngx_thread_mutex_unlock(&tp->mtx, tp->log);
return NULL;
}
}
task = tp->queue.first;
tp->queue.first = task->next;
if (tp->queue.first == NULL) {
tp->queue.last = &tp->queue.first;
}
if (ngx_thread_mutex_unlock(&tp->mtx, tp->log) != NGX_OK) {
return NULL;
}
#if 0
ngx_time_update();
#endif
ngx_log_debug2(NGX_LOG_DEBUG_CORE, tp->log, 0,
"run task #%ui in thread pool \"%V\"",
task->id, &tp->name);
task->handler(task->ctx, tp->log);
ngx_log_debug2(NGX_LOG_DEBUG_CORE, tp->log, 0,
"complete task #%ui in thread pool \"%V\"",
task->id, &tp->name);
task->next = NULL;
ngx_spinlock(&ngx_thread_pool_done_lock, 1, 2048);
*ngx_thread_pool_done.last = task;
ngx_thread_pool_done.last = &task->next;
ngx_memory_barrier();
ngx_unlock(&ngx_thread_pool_done_lock);
//处理task任务
(void) ngx_notify(ngx_thread_pool_handler);
}
}
线程主要执行的方法,参数的ev并没有实际用途,先用自旋锁保护done队列,从done队列上取出预处理过的task,这里之所以设计done队列,应该是为了各个线程的负载,这里task取出后会调用event事件的回调然后继续执行下一个task
//任务处理完后,epoll的通知读事件会调用该函数
ngx_notify通告主线程,该任务处理完毕,ngx_thread_pool_handler由主线程执行,也就是进程cycle{}通过epoll_wait返回执行,而不是由线程池中的线程执行
static void
ngx_thread_pool_handler(ngx_event_t *ev)
{
ngx_event_t *event;
ngx_thread_task_t *task;
ngx_log_debug0(NGX_LOG_DEBUG_CORE, ev->log, 0, "thread pool handler");
ngx_spinlock(&ngx_thread_pool_done_lock, 1, 2048);
/* 这里是不是有问题?
如果线程池中的线程执行任务比较快,而主进程在执行epoll_wait过程中有点阻塞,那么就检测不到ngx_notify中的epoll事件,有可能下次检测到该事件的时候
ngx_thread_pool_done上已经积累了很多执行完的任务事件,见ngx_thread_pool_cycle。
单这里好像只取了队列首部的任务啊?????? 队首外的任务丢弃了???????????不对吧
答案是,这里面所有的任务都在下面的while{}中得到了执行
*/
task = ngx_thread_pool_done.first;
ngx_thread_pool_done.first = NULL;
//尾部指向头,但是头已经变为空,即不执行任务
ngx_thread_pool_done.last = &ngx_thread_pool_done.first;
ngx_unlock(&ngx_thread_pool_done_lock);
while (task) {//遍历执行前面队列ngx_thread_pool_done中的每一个任务
ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0,
"run completion handler for task #%ui", task->id);
event = &task->event;
task = task->next;
event->complete = 1;
event->active = 0;
/*如果是小文件,则一次可以读完,函数指向可以参考ngx_http_cache_thread_handler ngx_http_copy_thread_handler ngx_thread_read
如果是大文件下载,则第一次走这里函数式上面的几个函数,但是由于一次最多获取32768字节,因此需要多次读取文件,就是由一次tread执行完任务后
触发ngx_notify通道epoll,然后走到这里继续读
*/
event->handler(event);//这里是否应该检查event->handler是否为空,例如参考ngx_thread_pool_destroy
}
将task放入pool的等待队列上,并唤醒
ngx_int_t
ngx_thread_task_post(ngx_thread_pool_t *tp, ngx_thread_task_t *task)
{
if (task->event.active) {
ngx_log_error(NGX_LOG_ALERT, tp->log, 0,
"task #%ui already active", task->id);
return NGX_ERROR;
}
if (ngx_thread_mutex_lock(&tp->mtx, tp->log) != NGX_OK) {
return NGX_ERROR;
}
if (tp->waiting >= tp->max_queue) {
(void) ngx_thread_mutex_unlock(&tp->mtx, tp->log);
ngx_log_error(NGX_LOG_ERR, tp->log, 0,
"thread pool \"%V\" queue overflow: %i tasks waiting",
&tp->name, tp->waiting);
return NGX_ERROR;
}
task->event.active = 1;
task->id = ngx_thread_pool_task_id++;
task->next = NULL;
if (ngx_thread_cond_signal(&tp->cond, tp->log) != NGX_OK) {
(void) ngx_thread_mutex_unlock(&tp->mtx, tp->log);
return NGX_ERROR;
}
*tp->queue.last = task;
tp->queue.last = &task->next;
tp->waiting++;
(void) ngx_thread_mutex_unlock(&tp->mtx, tp->log);
ngx_log_debug2(NGX_LOG_DEBUG_CORE, tp->log, 0,
"task #%ui added to thread pool \"%V\"",
task->id, &tp->name);
return NGX_OK;
}