Nginx源码解析——线程池

  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;
}

 

你可能感兴趣的:(Nginx)