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);
}
}
nginx源码分析线程池详解的更多相关文章
- nginx源码分析——线程池
源码: nginx 1.13.0-release 一.前言 nginx是采用多进程模型,master和worker之间主要通过pipe管道的方式进行通信,多进程的优势就在于各个进程互不影 ...
- JUC源码分析-线程池篇(三)Timer
JUC源码分析-线程池篇(三)Timer Timer 是 java.util 包提供的一个定时任务调度器,在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次. 1. Ti ...
- 【集合框架】JDK1.8源码分析之ArrayList详解(一)
[集合框架]JDK1.8源码分析之ArrayList详解(一) 一. 从ArrayList字表面推测 ArrayList类的命名是由Array和List单词组合而成,Array的中文意思是数组,Lis ...
- Elasticsearch源码分析—线程池(十一) ——就是从队列里处理请求
Elasticsearch源码分析—线程池(十一) 转自:https://www.felayman.com/articles/2017/11/10/1510291570687.html 线程池 每个节 ...
- JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor
JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor.它主要用来在 ...
- JUC源码分析-线程池篇(二)FutureTask
JUC源码分析-线程池篇(二)FutureTask JDK5 之后提供了 Callable 和 Future 接口,通过它们就可以在任务执行完毕之后得到任务的执行结果.本文从源代码角度分析下具体的实现 ...
- JUC源码分析-线程池篇(一):ThreadPoolExecutor
JUC源码分析-线程池篇(一):ThreadPoolExecutor Java 中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池.在开发过程中,合理地使用线程池 ...
- vuex 源码分析(六) 辅助函数 详解
对于state.getter.mutation.action来说,如果每次使用的时候都用this.$store.state.this.$store.getter等引用,会比较麻烦,代码也重复和冗余,我 ...
- vuex 源码分析(五) action 详解
action类似于mutation,不同的是Action提交的是mutation,而不是直接变更状态,而且action里可以包含任意异步操作,每个mutation的参数1是一个对象,可以包含如下六个属 ...
随机推荐
- 苹果CMS
本篇将主要讲解使用过程中普遍遇到的“问题”,这些问题并非是BUG,通常是需要我们自己去注意的一些点.(会结合用户反馈持续补充)http://www.maccms.com/doc/v10/faq.htm ...
- 13_数据的划分和介绍之sklearn数据集
1.数据集是如何划分?训练数据和评估数据不能使用相同数据,不然自己测自己,会使得准确率虚高,在遇到陌生数据时,不够准确. 2.数据集的获取: 通过load或者fetch方法. 3.数据集进行分割: 训 ...
- STL与泛型编程第一周作业
/* 题目: 给定一个 vector:v1 = [0, 0, 30, 20, 0, 0, 0, 0, 10, 0],希望通过not_equal_to 算法找到到不为零的元素,并复制到另一个 vecto ...
- 【深度学习】CNN 中 1x1 卷积核的作用
[深度学习]CNN 中 1x1 卷积核的作用 最近研究 GoogLeNet 和 VGG 神经网络结构的时候,都看见了它们在某些层有采取 1x1 作为卷积核,起初的时候,对这个做法很是迷惑,这是因为之前 ...
- linux 显示ip地址小工具-nali
1.下载软件包 wget http://qqwry.googlecode.com/files/nali-0.1.tar.gz 2.安装 tar -zxvf nali-0.2.tar.gz cd nal ...
- 在 Angularjs 中$state.go 如何传递参数
在目标页面规定接受的参数: .state('app.AttendanceEditFixed', { url: '/AttendanceEditFixed', params: {'id': null,' ...
- JasperReport查看和打印报告7
报表填充过程JasperPrint对象的输出可以使用内置的浏览器组件来查看,打印或导出到更多的流行的文件格式,如PDF,HTML,RTF,XLS,ODT,CSV或XML.Jasper文件查看和打印将包 ...
- HBase 三维模型解析
总结下一直想写hbase的实践经验,在用hbase的过程中,我们都知道,rowkey设计的好坏,是我们能最大发挥hbase的架构优势,也是我们是否正确理解hbase的一个关键点.闲话少说,进入正题. ...
- kuangbin带我飞QAQ DLX之一脸懵逼
1. hust 1017 DLX精确覆盖 模板题 勉强写了注释,但还是一脸懵逼,感觉插入方式明显有问题但又不知道哪里不对而且好像能得出正确结果真是奇了怪了 #include <iostream& ...
- Luogu P2486 [SDOI2011]染色(树链剖分+线段树合并)
Luogu P2486 [SDOI2011]染色 题面 题目描述 输入输出格式 输入格式: 输出格式: 对于每个询问操作,输出一行答案. 输入输出样例 输入样例: 6 5 2 2 1 2 1 1 1 ...