zoukankan      html  css  js  c++  java
  • nginx源码分析线程池详解

    nginx源码分析线程池详解

    一、前言
         nginx是采用多进程模型,master和worker之间主要通过pipe管道的方式进行通信,多进程的优势就在于各个进程互不影响。但是经常会有人问道,nginx为什么不采用多线程模型(这个除了之前一篇文章讲到的情况,别的只有去问作者了,HAHA)。其实,nginx代码中提供了一个thread_pool(线程池)的核心模块来处理多任务的。下面就本人对该thread_pool这个模块的理解来跟大家做些分享(文中错误、不足还请大家指出,谢谢) 
    二、thread_pool线程池模块介绍
         nginx的主要功能都是由一个个模块构成的,thread_pool也不例外。线程池主要用于读取、发送文件等IO操作,避免慢速IO影响worker的正常运行。先引用一段官方的配置示例

    Syntax: thread_pool name threads=number [max_queue=number];
    Default: thread_pool default threads=32 max_queue=65536;
    Context: main
         根据上述的配置说明,thread_pool是有名字的,上面的线程数目以及队列大小都是指每个worker进程中的线程,而不是所有worker中线程的总数。一个线程池中所有的线程共享一个队列,队列中的最大人数数量为上面定义的max_queue,如果队列满了的话,再往队列中添加任务就会报错。 
         根据之前讲到过的模块初始化流程(在master启动worker之前) create_conf--> command_set函数-->init_conf,下面就按照这个流程看看thread_pool模块的初始化

    /******************* nginx/src/core/ngx_thread_pool.c ************************/
    //创建线程池所需的基础结构
    static void * ngx_thread_pool_create_conf(ngx_cycle_t *cycle)
    {
    ngx_thread_pool_conf_t *tcf;
    //从cycle->pool指向的内存池中申请一块内存
    tcf = ngx_pcalloc(cycle->pool, sizeof(ngx_thread_pool_conf_t));
    if (tcf == NULL) {
    return NULL;
    }

    //先申请包含4个ngx_thread_pool_t指针类型元素的数组
    //ngx_thread_pool_t结构体中保存了一个线程池相关的信息
    if (ngx_array_init(&tcf->pools, cycle->pool, 4,sizeof(ngx_thread_pool_t *))
    != NGX_OK)
    {
    return NULL;
    }

    return tcf;
    }

    //解析处理配置文件中thread_pool的配置,并将相关信息保存的ngx_thread_pool_t中
    static char * ngx_thread_pool(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
    {
    ngx_str_t *value;
    ngx_uint_t i;
    ngx_thread_pool_t *tp;

    value = cf->args->elts;

    //根据thread_pool配置中的name作为线程池的唯一标识(如果重名,只有第一个有效)
    //申请ngx_thread_pool_t结构保存线程池的相关信息
    //由此可见,nginx支持配置多个name不同的线程池
    tp = ngx_thread_pool_add(cf, &value[1]);
    .......
    //处理thread_pool配置行的所有元素
    for (i = 2; i < cf->args->nelts; i++) {
    //检查配置的线程数
    if (ngx_strncmp(value[i].data, "threads=", 8) == 0) {
    .......
    }

    //检查配置的最大队列长度
    if (ngx_strncmp(value[i].data, "max_queue=", 10) == 0) {
    .......
    }
    }
    ......
    }

    //判断包含多个线程池的数组中的各个线程池的配置是否正确
    static char * ngx_thread_pool_init_conf(ngx_cycle_t *cycle, void *conf)
    {
    ....
    ngx_thread_pool_t **tpp;

    tpp = tcf->pools.elts;
    //遍历数组中所有的线程池配置,并检查其正确性
    for (i = 0; i < tcf->pools.nelts; i++) {
    .....
    }

    return NGX_CONF_OK;
    }
         在上述的流程走完之后,nginx的master就保存了一份所有线程池的配置(tcf->pools),这份配置在创建worker时也会被继承。然后每个worker中都调用各个核心模块的init_process函数(如果有的话)。

    /******************* nginx/src/core/ngx_thread_pool.c ************************/
    //创建线程池所需的基础结构
    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)
    {
    .....
    //初始化任务队列
    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;
    }
    ......
    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;
    }
    }
    ......
    }

    //线程池中线程处理主函数
    static void *ngx_thread_pool_cycle(void *data)
    {
    ......
    for ( ;; ) {
    //阻塞的方式获取线程锁
    if (ngx_thread_mutex_lock(&tp->mtx, tp->log) != NGX_OK) {
    return NULL;
    }

    /* the number may become negative */
    tp->waiting--;

    //如果任务队列为空,就cond_wait阻塞等待有新任务时调用cond_signal/broadcast触发
    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,并将其从队列中移除
    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;
    }
    ......
    //task的处理函数
    task->handler(task->ctx, tp->log);
    .....

    ngx_spinlock(&ngx_thread_pool_done_lock, 1, 2048);

    //将经过预处理的任务添加到done队列中等待调用event的回调函数继续处理
    *ngx_thread_pool_done.last = task;
    ngx_thread_pool_done.last = &task->next;

    //防止编译器优化,保证解锁操作是在上述语句执行完毕后再去执行的
    ngx_memory_barrier();

    ngx_unlock(&ngx_thread_pool_done_lock);

    (void) ngx_notify(ngx_thread_pool_handler);
    }
    }

    //处理pool_done队列上task中包含的每个event事件
    static void ngx_thread_pool_handler(ngx_event_t *ev)
    {
    .....
    ngx_spinlock(&ngx_thread_pool_done_lock, 1, 2048);

    //获取任务链表的头部
    task = ngx_thread_pool_done.first;
    ngx_thread_pool_done.first = NULL;
    ngx_thread_pool_done.last = &ngx_thread_pool_done.first;

    ngx_memory_barrier();

    ngx_unlock(&ngx_thread_pool_done_lock);

    while (task) {
    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;

    //调用event对应的处理函数有针对性的进行处理
    event->handler(event);
    }
    }

  • 相关阅读:
    肯恩·威尔伯:整合灵性途径的几大障碍
    金刚经里面的「应无所住而生其心」,这句话怎么理解?
    成熟是人一辈子的修养,与年龄无关
    杰克·康菲尔德:灵性成熟的十个特质
    杰克·康菲尔德:我是谁?(强烈推荐)
    x11vnc:利用远程机器上的X-Server来进行VNC连接(x11vnc 和 x-server 在不同的机器上)
    Linux ALL:Tigervnc-Server
    Centos 6/Redhat 6:远程图形桌面: tigervnc
    Fedora 31 :远程图形桌面: tigervnc
    win server 2019 :【2个?远程管理用户(单会话?)】升级为远程桌面服务【多用户(多会话)】
  • 原文地址:https://www.cnblogs.com/2881064178dinfeng/p/7002703.html
Copyright © 2011-2022 走看看