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

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

  1. /******************* nginx/src/core/ngx_thread_pool.c ************************/
  2. //创建线程池所需的基础结构
  3. static void * ngx_thread_pool_create_conf(ngx_cycle_t *cycle)
  4. {
  5. ngx_thread_pool_conf_t *tcf;
  6. //从cycle->pool指向的内存池中申请一块内存
  7. tcf = ngx_pcalloc(cycle->pool, sizeof(ngx_thread_pool_conf_t));
  8. if (tcf == NULL) {
  9. return NULL;
  10. }
  11.  
  12. //先申请包含4个ngx_thread_pool_t指针类型元素的数组
  13. //ngx_thread_pool_t结构体中保存了一个线程池相关的信息
  14. if (ngx_array_init(&tcf->pools, cycle->pool, 4,
  15. sizeof(ngx_thread_pool_t *))
  16. != NGX_OK)
  17. {
  18. return NULL;
  19. }
  20.  
  21. return tcf;
  22. }
  23.  
  24. //解析处理配置文件中thread_pool的配置,并将相关信息保存的ngx_thread_pool_t中
  25. static char * ngx_thread_pool(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
  26. {
  27. ngx_str_t *value;
  28. ngx_uint_t i;
  29. ngx_thread_pool_t *tp;
  30.  
  31. value = cf->args->elts;
  32.  
  33. //根据thread_pool配置中的name作为线程池的唯一标识(如果重名,只有第一个有效)
  34. //申请ngx_thread_pool_t结构保存线程池的相关信息
  35. //由此可见,nginx支持配置多个name不同的线程池
  36. tp = ngx_thread_pool_add(cf, &value[1]);
  37. .......
  38. //处理thread_pool配置行的所有元素
  39. for (i = 2; i < cf->args->nelts; i++) {
  40. //检查配置的线程数
  41. if (ngx_strncmp(value[i].data, "threads=", 8) == 0) {
  42. .......
  43. }
  44.  
  45. //检查配置的最大队列长度
  46. if (ngx_strncmp(value[i].data, "max_queue=", 10) == 0) {
  47. .......
  48. }
  49. }
  50. ......
  51. }
  52.  
  53. //判断包含多个线程池的数组中的各个线程池的配置是否正确
  54. static char * ngx_thread_pool_init_conf(ngx_cycle_t *cycle, void *conf)
  55. {
  56. ....
  57. ngx_thread_pool_t **tpp;
  58.  
  59. tpp = tcf->pools.elts;
  60. //遍历数组中所有的线程池配置,并检查其正确性
  61. for (i = 0; i < tcf->pools.nelts; i++) {
  62. .....
  63. }
  64.  
  65. return NGX_CONF_OK;
  66. }
 
     在上述的流程走完之后,nginx的master就保存了一份所有线程池的配置(tcf->pools),这份配置在创建worker时也会被继承。然后每个worker中都调用各个核心模块的init_process函数(如果有的话)。

  1. /******************* nginx/src/core/ngx_thread_pool.c ************************/
  2. //创建线程池所需的基础结构
  3. static ngx_int_t
  4. ngx_thread_pool_init_worker(ngx_cycle_t *cycle)
  5. {
  6. ngx_uint_t i;
  7. ngx_thread_pool_t **tpp;
  8. ngx_thread_pool_conf_t *tcf;
  9. //如果不是worker或者只有一个worker就不起用线程池
  10. if (ngx_process != NGX_PROCESS_WORKER
  11. && ngx_process != NGX_PROCESS_SINGLE)
  12. {
  13. return NGX_OK;
  14. }
  15.  
  16. //初始化任务队列
  17. ngx_thread_pool_queue_init(&ngx_thread_pool_done);
  18.  
  19. tpp = tcf->pools.elts;
  20. for (i = 0; i < tcf->pools.nelts; i++) {
  21. //初始化各个线程池
  22. if (ngx_thread_pool_init(tpp[i], cycle->log, cycle->pool) != NGX_OK) {
  23. return NGX_ERROR;
  24. }
  25. }
  26.  
  27. return NGX_OK;
  28. }
  29.  
  30. //线程池初始化
  31. static ngx_int_t ngx_thread_pool_init(ngx_thread_pool_t *tp, ngx_log_t *log, ngx_pool_t *pool)
  32. {
  33. .....
  34. //初始化任务队列
  35. ngx_thread_pool_queue_init(&tp->queue);
  36.  
  37. //创建线程锁
  38. if (ngx_thread_mutex_create(&tp->mtx, log) != NGX_OK) {
  39. return NGX_ERROR;
  40. }
  41.  
  42. //创建线程条件变量
  43. if (ngx_thread_cond_create(&tp->cond, log) != NGX_OK) {
  44. (void) ngx_thread_mutex_destroy(&tp->mtx, log);
  45. return NGX_ERROR;
  46. }
  47. ......
  48. for (n = 0; n < tp->threads; n++) {
  49. //创建线程池中的每个线程
  50. err = pthread_create(&tid, &attr, ngx_thread_pool_cycle, tp);
  51. if (err) {
  52. ngx_log_error(NGX_LOG_ALERT, log, err,
  53. "pthread_create() failed");
  54. return NGX_ERROR;
  55. }
  56. }
  57. ......
  58. }
  59.  
  60. //线程池中线程处理主函数
  61. static void *ngx_thread_pool_cycle(void *data)
  62. {
  63. ......
  64. for ( ;; ) {
  65. //阻塞的方式获取线程锁
  66. if (ngx_thread_mutex_lock(&tp->mtx, tp->log) != NGX_OK) {
  67. return NULL;
  68. }
  69.  
  70. /* the number may become negative */
  71. tp->waiting--;
  72.  
  73. //如果任务队列为空,就cond_wait阻塞等待有新任务时调用cond_signal/broadcast触发
  74. while (tp->queue.first == NULL) {
  75. if (ngx_thread_cond_wait(&tp->cond, &tp->mtx, tp->log)
  76. != NGX_OK)
  77. {
  78. (void) ngx_thread_mutex_unlock(&tp->mtx, tp->log);
  79. return NULL;
  80. }
  81. }
  82. //从任务队列中获取task,并将其从队列中移除
  83. task = tp->queue.first;
  84. tp->queue.first = task->next;
  85.  
  86. if (tp->queue.first == NULL) {
  87. tp->queue.last = &tp->queue.first;
  88. }
  89.  
  90. if (ngx_thread_mutex_unlock(&tp->mtx, tp->log) != NGX_OK) {
  91. return NULL;
  92. }
  93. ......
  94. //task的处理函数
  95. task->handler(task->ctx, tp->log);
  96. .....
  97.  
  98. ngx_spinlock(&ngx_thread_pool_done_lock, 1, 2048);
  99.  
  100. //将经过预处理的任务添加到done队列中等待调用event的回调函数继续处理
  101. *ngx_thread_pool_done.last = task;
  102. ngx_thread_pool_done.last = &task->next;
  103.  
  104. //防止编译器优化,保证解锁操作是在上述语句执行完毕后再去执行的
  105. ngx_memory_barrier();
  106.  
  107. ngx_unlock(&ngx_thread_pool_done_lock);
  108.  
  109. (void) ngx_notify(ngx_thread_pool_handler);
  110. }
  111. }
  112.  
  113. //处理pool_done队列上task中包含的每个event事件
  114. static void ngx_thread_pool_handler(ngx_event_t *ev)
  115. {
  116. .....
  117. ngx_spinlock(&ngx_thread_pool_done_lock, 1, 2048);
  118.  
  119. //获取任务链表的头部
  120. task = ngx_thread_pool_done.first;
  121. ngx_thread_pool_done.first = NULL;
  122. ngx_thread_pool_done.last = &ngx_thread_pool_done.first;
  123.  
  124. ngx_memory_barrier();
  125.  
  126. ngx_unlock(&ngx_thread_pool_done_lock);
  127.  
  128. while (task) {
  129. ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0,
  130. "run completion handler for task #%ui", task->id);
  131. //遍历队列中的所有任务事件
  132. event = &task->event;
  133. task = task->next;
  134.  
  135. event->complete = 1;
  136. event->active = 0;
  137.  
  138. //调用event对应的处理函数有针对性的进行处理
  139. event->handler(event);
  140. }
  141. }
 
三、thread_pool线程池使用示例
     根据之前所讲到的,nginx中的线程池主要是用于操作文件的IO操作。所以,在nginx中自带的模块ngx_http_file_cache.c文件中看到了线程池的使用。

  1. /*********************** nginx/src/os/unix/ngx_files.c **********************/
  2. //file_cache模块的处理函数(涉及到了线程池)
  3. static ssize_t ngx_http_file_cache_aio_read(ngx_http_request_t *r, ngx_http_cache_t *c)
  4. {
  5. .......
  6. #if (NGX_THREADS)
  7.  
  8. if (clcf->aio == NGX_HTTP_AIO_THREADS) {
  9. c->file.thread_task = c->thread_task;
  10. //这里注册的函数在下面语句中的ngx_thread_read函数中被调用
  11. c->file.thread_handler = ngx_http_cache_thread_handler;
  12. c->file.thread_ctx = r;
  13. //根据任务的属性,选择正确的线程池,并初始化task结构体中的各个成员
  14. n = ngx_thread_read(&c->file, c->buf->pos, c->body_start, 0, r->pool);
  15.  
  16. c->thread_task = c->file.thread_task;
  17. c->reading = (n == NGX_AGAIN);
  18.  
  19. return n;
  20. }
  21. #endif
  22.  
  23. return ngx_read_file(&c->file, c->buf->pos, c->body_start, 0);
  24. }
  25.  
  26. //task任务的处理函数
  27. static ngx_int_t ngx_http_cache_thread_handler(ngx_thread_task_t *task, ngx_file_t *file)
  28. {
  29. .......
  30. tp = clcf->thread_pool;
  31. .......
  32.  
  33. task->event.data = r;
  34. //注册thread_event_handler函数,该函数在处理pool_done队列中event事件时被调用
  35. task->event.handler = ngx_http_cache_thread_event_handler;
  36.  
  37. //将任务放到线程池的任务队列中
  38. if (ngx_thread_task_post(tp, task) != NGX_OK) {
  39. return NGX_ERROR;
  40. }
  41. ......
  42. }
  43.  
  44. /*********************** nginx/src/core/ngx_thread_pool.c **********************/
  45. //添加任务到队列中
  46. ngx_int_t ngx_thread_task_post(ngx_thread_pool_t *tp, ngx_thread_task_t *task)
  47. {
  48. //如果当前的任务正在处理就退出
  49. if (task->event.active) {
  50. ngx_log_error(NGX_LOG_ALERT, tp->log, 0,
  51. "task #%ui already active", task->id);
  52. return NGX_ERROR;
  53. }
  54.  
  55. if (ngx_thread_mutex_lock(&tp->mtx, tp->log) != NGX_OK) {
  56. return NGX_ERROR;
  57. }
  58.  
  59. //判断当前线程池等待的任务数量与最大队列长度的关系
  60. if (tp->waiting >= tp->max_queue) {
  61. (void) ngx_thread_mutex_unlock(&tp->mtx, tp->log);
  62.  
  63. ngx_log_error(NGX_LOG_ERR, tp->log, 0,
  64. "thread pool \"%V\" queue overflow: %i tasks waiting",
  65. &tp->name, tp->waiting);
  66. return NGX_ERROR;
  67. }
  68. //激活任务
  69. task->event.active = 1;
  70.  
  71. task->id = ngx_thread_pool_task_id++;
  72. task->next = NULL;
  73.  
  74. //通知阻塞的线程有新事件加入,可以解除阻塞
  75. if (ngx_thread_cond_signal(&tp->cond, tp->log) != NGX_OK) {
  76. (void) ngx_thread_mutex_unlock(&tp->mtx, tp->log);
  77. return NGX_ERROR;
  78. }
  79.  
  80. *tp->queue.last = task;
  81. tp->queue.last = &task->next;
  82.  
  83. tp->waiting++;
  84.  
  85. (void) ngx_thread_mutex_unlock(&tp->mtx, tp->log);
  86.  
  87. ngx_log_debug2(NGX_LOG_DEBUG_CORE, tp->log, 0,
  88. "task #%ui added to thread pool \"%V\"",
  89. task->id, &tp->name);
  90.  
  91. return NGX_OK;
  92. }
 
    上面示例基本展示了nginx目前对线程池的使用方法,采用线程池来处理IO这类慢速操作可以提升worker的主线程的执行效率。当然,用户自己在开发模块时,也可以参照file_cache模块中使用线程池的方法来调用多线程提升程序性能。(欢迎大家多多批评指正)
 

nginx源码分析——线程池的更多相关文章

  1. nginx源码分析线程池详解

    nginx源码分析线程池详解 一.前言     nginx是采用多进程模型,master和worker之间主要通过pipe管道的方式进行通信,多进程的优势就在于各个进程互不影响.但是经常会有人问道,n ...

  2. Elasticsearch源码分析—线程池(十一) ——就是从队列里处理请求

    Elasticsearch源码分析—线程池(十一) 转自:https://www.felayman.com/articles/2017/11/10/1510291570687.html 线程池 每个节 ...

  3. JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor

    JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor.它主要用来在 ...

  4. JUC源码分析-线程池篇(二)FutureTask

    JUC源码分析-线程池篇(二)FutureTask JDK5 之后提供了 Callable 和 Future 接口,通过它们就可以在任务执行完毕之后得到任务的执行结果.本文从源代码角度分析下具体的实现 ...

  5. JUC源码分析-线程池篇(三)Timer

    JUC源码分析-线程池篇(三)Timer Timer 是 java.util 包提供的一个定时任务调度器,在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次. 1. Ti ...

  6. JUC源码分析-线程池篇(一):ThreadPoolExecutor

    JUC源码分析-线程池篇(一):ThreadPoolExecutor Java 中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池.在开发过程中,合理地使用线程池 ...

  7. nginx源码分析—内存池结构ngx_pool_t及内存管理

    Content 0. 序 1. 内存池结构 1.1 ngx_pool_t结构 1.2 其他相关结构 1.3 ngx_pool_t的逻辑结构 2. 内存池操作 2.1 创建内存池 2.2 销毁内存池 2 ...

  8. nginx源码分析——内存池

    内存池的目的就是管理内存,使回收内存可以自动化一些. ngx_palloc.h /* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. * ...

  9. Nginx源码分析:3张图看懂启动及进程工作原理

    编者按:高可用架构分享及传播在架构领域具有典型意义的文章,本文由陈科在高可用架构群分享.转载请注明来自高可用架构公众号「ArchNotes」.   导读:很多工程师及架构师都希望了解及掌握高性能服务器 ...

随机推荐

  1. 通过Elasticsearch使用的你的数据

    Elasticsearch 系列导航 elasticsearch 与 elasticsearch-head 的安装 ElasticSearch Index API && Mapping ...

  2. win10 平台 elasticsearch 与 elasticsearch-head 的安装

    由于elasticsearch是基于java开发的,所以 第一步需要安装JDK. 具体JDK的安装步骤  http://jingyan.baidu.com/article/6dad5075d1dc40 ...

  3. c# 逆波兰式实现计算器

    语文不好,不太会组织语言,希望不要太在意. 如题,先简要介绍一下什么是逆波兰式  通常我们在写数学公式的时候  就是a+b+c这样,这种表达式称为中缀表达式,逆波兰式又称为后缀表达式,例如a+b 后缀 ...

  4. Kindle PaperWhite3 越狱和PDF插件的安装

    下载所需工具 这里分享的文件是这个教程中所需要的所有文件 所有工具下载链接:http://pan.baidu.com/s/1c249P2S 密码:ozc7 一.准备工作 本越狱方法仅适用于 KO.KV ...

  5. MyRocks DDL原理

    最近一个日常实例在做DDL过程中,直接把数据库给干趴下了,问题还是比较严重的,于是赶紧排查问题,撸了下crash堆栈和alert日志,发现是在去除唯一约束的场景下,MyRocks存在一个严重的bug, ...

  6. 第五章 HQL实用技术

    第五章   HQL实用技术5.1  使用HQL查询语句(面向对象查询语句)    5.1.1 编写HQL语句        5.1.1.1 from子句                    例:fr ...

  7. JS获取URL中参数值(QueryString)的4种方法

    方法一:正则法 function getQueryString(name) {    var reg = new RegExp('(^|&)' + name + '=([^&]*)(& ...

  8. 【BFS + Hash】拼图——携程2017春招编程题2

    写在前面 前天参加了携程的网测--还是感觉自己太!渣!了!    _(:з」∠)_ 时光匆匆啊,已经到了开始思考人生的时候了(算了不矫情了)--总之写个博客来督促一下自己.之前太懒了,很多时候都是输在 ...

  9. 在Ubuntu中使用JAVA与tomcat搭建web服务器

    一:材料 1.操作系统:ubuntu16.04 2.JAVA: jdk1.8.0 3.Tomcat:tomcat 8 4.域名:zhuandshao.cn 二:过程 1.安装java 1)在官网下载j ...

  10. SELECT中(非常)常用的子查询操作

    MySQL中的子查询 是在MySQL中经常使用到的一个操作,不仅仅是用在DQL语句中,在DDL语句.DML语句中也都会常用到子查询. 子查询的定义: 子查询是将一个查询语句嵌套在另一个查询语句中: 在 ...