并发基本概念
并发 :在同一时间段内,多个任务同时执行,偏向于多个任务交替执行,在某一时刻其实只有一个任务在执行(单个CPU就可并发,比如时间片轮转机制)。
并行:同一时刻,多个任务同时执行(并行需要有多个CPU)。
处理事件过程"阻塞"怎么办?
1.忙于漫长的 CPU 密集型处理;
2.读取文件,但文件尚未缓存,从硬盘中读取较为缓慢;
3.不得不等待获取某个资源:硬件驱动,网络上的请求和响应,互斥锁,等待同步方式调用的数据库响应等;
单个进程或线程同时只能处理一个任务,如果有很多请求需要同时处理怎么办?
运用多进程或多线程技术解决 ,但仍存在缺陷:
1.创建和销毁线程上花费的时间和消耗的系统资源,甚至可能要比花在处理实际的用户请求的时间和资源要多得多;
2.活动的线程需要消耗系统资源,如果启动太多,会导致系统由于过度消耗内存或"切换过度"而导致系统资源不足;
Nginx 线程池技术
线程池 - 由一个任务队列和一组处理队列的线程组成。一旦工作进程需要处理某个可能"阻塞"的操作,不用自己操作,将其作为一个任务放到线程池的队列,接着会被某个空闲线程提取处理。
任务 :待处理的工作,通常由标识、上下文和处理函数;
任务队列 :按顺序保存待处理的任务队列,等待线程中的线程组处理;
线程池 :由多个已启动的一组线程组成;
条件变量 :一种同步机制,允许线程挂起,直到共享数据上的某些条件得到满足;
互斥锁:保证在任一时刻,只能有一个线程访问对象;
ngx_thread_pool_t 结构体
cpp
struct ngx_thread_pool_s {
ngx_thread_mutex_t mtx;
ngx_thread_pool_queue_t queue;
ngx_int_t waiting;
ngx_thread_cond_t cond;
ngx_log_t *log;
ngx_str_t name;
ngx_uint_t threads;
ngx_int_t max_queue;
u_char *file;
ngx_uint_t line;
};
mtx
: 互斥锁,用于锁定任务队列,避免竞争状态。queue
: 任务队列。waiting
: 有多少个任务正在等待处理。cond
: 用于通知线程池有任务需要处理。name
: 线程池名称。threads
: 线程池由多少个线程组成(线程数)。max_queue
: 线程池最大能处理的任务数。
ngx_thread_task_t 结构体
cpp
struct thread_task_s {
thread_task_t *next;
uint_t id;
void *ctx; //上下文
void (*handler)(void *data);
};
next
: 指向下一个任务。id
: 任务ID。ctx
: 任务的上下文。handler
: 处理任务的函数句柄。event
: 跟任务关联的事件对象(当线程池处理成任务之后将会由主线程调用event对象的handler回调函数)。
线程池初始化
在 Nginx 启动的时候,首先会调用 ngx_thread_pool_init_worker()
函数来初始化线程池。ngx_thread_pool_init_worker()
函数最终会调用 ngx_thread_pool_init()
,源码如下:
cpp
static ngx_int_t
ngx_thread_pool_init(ngx_thread_pool_t *tp, ngx_log_t *log, ngx_pool_t *pool)
{
...
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;
}
}
...
return NGX_OK;
}
ngx_thread_pool_init()
最终调用pthread_create()
函数创建线程池中的工作线程,工作线程会从ngx_thread_pool_cycle()
函数开始执行。
ngx_thread_pool_cycle()函数源码如下:
cpp
static void *
ngx_thread_pool_cycle(void *data)
{
...
for ( ;; ) {
if (ngx_thread_mutex_lock(&tp->mtx, tp->log) != NGX_OK) {
return NULL;
}
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;
}
// 处理任务
task->handler(task->ctx, tp->log);
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_unlock(&ngx_thread_pool_done_lock);
(void) ngx_notify(ngx_thread_pool_handler); // 通知主线程
}
}
ngx_thread_pool_cycle()
函数的主要工作是从待处理的任务队列中获取一个任务,然后调用任务对象的handler()
函数处理任务,完成后把任务放置到完成队列中,并通过ngx_notify()
通知主线程。
添加任务到任务队列
任务是主线程创建的(主线程负责处理客户端请求)
主线程通过ngx_thread_task_post()
函数向任务队列中添加一个任务,代码如下:
cpp
ngx_int_t
ngx_thread_task_post(ngx_thread_pool_t *tp, ngx_thread_task_t *task)
{
...
if (ngx_thread_mutex_lock(&tp->mtx, tp->log) != NGX_OK) {
return NGX_ERROR;
}
// 通知线程池有任务需要处理
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);
return NGX_OK;
}
ngx_thread_task_post()
函数首先调用 ngx_thread_cond_signal()
通知线程池的线程有任务需要处理,然后把任务添加到任务队列中。
先通知线程池在添加任务到任务队列中会不会有顺序问题?
其实这样做是没问题的,这是因为只要主线程不调用ngx_thread_mutex_unlock()
把互斥锁解开,线程池中的工作线程是不会从ngx_thread_cond_wait()
返回的。
收尾工作
当线程池把任务处理完后会把其放置到完成队列中(ngx_thread_pool_done
),然后调用ngx_notify()
通知主线程有任务完成了。主线程收到通知后,会在事件模块中进行收尾工作:调用task.event.handler()
。task.event.handler
由任务创建者设置。
Nginx 实现
封装互斥锁&条件变量
cpp
//thread.h
#ifndef _DEMO_THREAD_H_INCLUDED_
#define _DEMO_THREAD_H_INCLUDED_
// 当C++程序调用这个代码的时候要声明下方的函数都以C语言方式编译
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/types.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
typedef intptr_t int_t;
typedef uintptr_t uint_t;
#define OK 0
#define ERROR -1
// 对互斥量进行了封装 只能有一个线程拿到锁
int thread_mutex_create(pthread_mutex_t *mtx);
int thread_mutex_destroy(pthread_mutex_t *mtx);
int thread_mutex_lock(pthread_mutex_t *mtx);
int thread_mutex_unlock(pthread_mutex_t *mtx);
// 对条件变量进行了封装 上锁后,阻塞等待信号发生,这样就可以触发任务
int thread_cond_create(pthread_cond_t *cond);
int thread_cond_destroy(pthread_cond_t *cond);
int thread_cond_signal(pthread_cond_t *cond);
int thread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mtx);
#ifdef __cplusplus
}
#endif
#endif /* _DEMO_THREAD_H_INCLUDED_ */
cpp
//thread_mutex.c
#include "thread.h"
int
thread_mutex_create(pthread_mutex_t *mtx)
{
int err;
pthread_mutexattr_t attr; // 互斥量属性
err = pthread_mutexattr_init(&attr);
if (err != 0) {
// 向标准出错中输入
fprintf(stderr, "pthread_mutexattr_init() failed, reason: %s\n", strerror(errno));
return ERROR;
}
/*
PTHREAD_MUTEX_ERRORCHECK:检测锁 就是防止死锁发生
如果这个线程已经拿到锁了,然后还申请拿锁,如果不做处理就会照成死锁
遇到这个情况设置PTHREAD_MUTEX_ERRORCHECK属性就会报错
*/
err = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
if (err != 0) {
fprintf(stderr, "pthread_mutexattr_settype(PTHREAD_MUTEX_ERRORCHECK) failed, reason: %s\n", strerror(errno));
return ERROR;
}
// 使用 attr 初始化mtx锁 初始化后销毁 attr
err = pthread_mutex_init(mtx, &attr);
if (err != 0) {
fprintf(stderr, "pthread_mutex_init() failed, reason: %s\n", strerror(errno));
return ERROR;
}
err = pthread_mutexattr_destroy(&attr);
if (err != 0) {
fprintf(stderr, "pthread_mutexattr_destroy() failed, reason: %s\n", strerror(errno));
}
return OK;
}
// 销毁互斥量
int
thread_mutex_destroy(pthread_mutex_t *mtx)
{
int err;
err = pthread_mutex_destroy(mtx);
if (err != 0) {
fprintf(stderr, "pthread_mutex_destroy() failed, reason: %s\n", strerror(errno));
return ERROR;
}
return OK;
}
// 上锁
int
thread_mutex_lock(pthread_mutex_t *mtx)
{
int err;
err = pthread_mutex_lock(mtx);
if (err == 0) {
return OK;
}
fprintf(stderr, "pthread_mutex_lock() failed, reason: %s\n", strerror(errno));
return ERROR;
}
// 解锁
int
thread_mutex_unlock(pthread_mutex_t *mtx)
{
int err;
err = pthread_mutex_unlock(mtx);
#if 0
ngx_time_update();
#endif
if (err == 0) {
return OK;
}
fprintf(stderr, "pthread_mutex_unlock() failed, reason: %s\n", strerror(errno));
return ERROR;
}
cpp
//thread_cond.c
#include "thread.h"
// 对条件变量进行了封装
int
thread_cond_create(pthread_cond_t *cond)
{
int err;
// 参数:cond: 条件变量指针 attr:条件变量高级属性
err = pthread_cond_init(cond, NULL);
if (err == 0) {
return OK;
}
fprintf(stderr, "pthread_cond_init() failed, reason: %s\n",strerror(errno));
return ERROR;
}
int
thread_cond_destroy(pthread_cond_t *cond)
{
int err;
err = pthread_cond_destroy(cond);
if (err == 0) {
return OK;
}
fprintf(stderr, "pthread_cond_destroy() failed, reason: %s\n",strerror(errno));
return ERROR;
}
int
thread_cond_signal(pthread_cond_t *cond)
{
int err;
err = pthread_cond_signal(cond);
if (err == 0) {
return OK;
}
fprintf(stderr, "pthread_cond_signal() failed, reason: %s\n",strerror(errno));
return ERROR;
}
int
thread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mtx)
{
int err;
err = pthread_cond_wait(cond, mtx);
if (err == 0) {
return OK;
}
fprintf(stderr, "pthread_cond_wait() failed, reason: %s\n",strerror(errno));
return ERROR;
}
线程池实现
cpp
//thread_pool.h
#ifndef _THREAD_POOL_H_INCLUDED_
#define _THREAD_POOL_H_INCLUDED_
// 因为我们执行的是cpp程序,所以要告诉编译器下方的函数都以C语言方式编译
#ifdef __cplusplus
extern "C" {
#endif
#include "thread.h"
/*
1.线程数太多,导致线程切换比较多,所以效率比较低,
但是如果阻塞的任务比较多,那么多开点线程,就会比较快
2.任务队列不可以无限多,因为每一个任务都占有内存,内存不可能无限多
*/
#define DEFAULT_THREADS_NUM 8 // 默认线程数8,因为我电脑核数为8
#define DEFAULT_QUEUE_NUM 65535 // 任务队列最大容量
typedef unsigned long atomic_uint_t;
typedef struct thread_task_s thread_task_t;// 线程任务
typedef struct thread_pool_s thread_pool_t;// 线程池
// 任务结构体
struct thread_task_s {
thread_task_t *next; // 链表的下一个节点
uint_t id; // 每一个任务都有一个id
void *ctx; // 处理函数的参数
void(*handler)(void *data); // 指向任务处理函数
};
typedef struct {
thread_task_t *first;
thread_task_t **last;// 指向最后一个节点,插入的时候直接使用这个
} thread_pool_queue_t;
// thread_pool_queue_init(q); 相当于:(q)->first = NULL;(q)->last = &(q)->first;
// 以后插入任务的时候只需要 last->next = 任务 , 不需要动用first
#define thread_pool_queue_init(q) \
(q)->first = NULL; \
(q)->last = &(q)->first
struct thread_pool_s {
pthread_mutex_t mtx; // 互斥锁
thread_pool_queue_t queue; // 任务队列
int_t waiting;// 没有处理的任务数
pthread_cond_t cond; // 条件变量
char *name; // 线程池的名字
uint_t threads;// 线程池中线程数量
int_t max_queue;// 队列的长度,队列中任务的容纳量
};
thread_task_t *thread_task_alloc(size_t size);// 给任务和处理任务的函数参数分配内存
void thread_task_free(thread_task_t* task);// 释放内存
int_t thread_task_post(thread_pool_t *tp, thread_task_t *task);// 把任务放入线程池
thread_pool_t* thread_pool_init();// 对线程池初始化
void thread_pool_destroy(thread_pool_t *tp);// 销毁线程池
#ifdef __cplusplus
}
#endif
#endif /* _THREAD_POOL_H_INCLUDED_ */
cpp
//thread_pool.cpp
#include "thread_pool.h"
static void thread_pool_exit_handler(void *data);// 线程自杀
static void *thread_pool_cycle(void *data);//线程池的主循环
static int_t thread_pool_init_default(thread_pool_t *tpp, char *name);// 线程池默认参数
static uint_t thread_pool_task_id;
static int debug = 0;
thread_pool_t* thread_pool_init()
{
int err;
pthread_t tid;
uint_t n;
pthread_attr_t attr;
thread_pool_t *tp=NULL;
// 使用calloc初始化内存,初始内存会置零
tp = (thread_pool_t*)calloc(1,sizeof(thread_pool_t));
if(tp == NULL){
fprintf(stderr, "thread_pool_init: calloc failed!\n");
return NULL;
}
thread_pool_init_default(tp, NULL);// 初始化线程池
thread_pool_queue_init(&tp->queue);// 会使用宏定义替换
// 创建互斥锁和条件变量
if (thread_mutex_create(&tp->mtx) != OK) {
free(tp);
return NULL;
}
if (thread_cond_create(&tp->cond) != OK) {
(void) thread_mutex_destroy(&tp->mtx);
free(tp);
return NULL;
}
err = pthread_attr_init(&attr);// 给线程做初始化
if (err) {
fprintf(stderr, "pthread_attr_init() failed, reason: %s\n",strerror(errno));
free(tp);
return NULL;
}
/*
PTHREAD_CREATE_DETACHED:意思就是和主线程断绝关系,主线程使用pthread_join 无法等待到结束的子进程
*/
err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (err) {
fprintf(stderr, "pthread_attr_setdetachstate() failed, reason: %s\n",strerror(errno));
free(tp);
return NULL;
}
/*
原型:int pthread_create (pthread_t *thread,pthread_attr_t *attr,
void *(*start_routine)(void*), void *arg);
参数:thread, 指向新线程的标识符。是一个传出参数
attr, 用来设置新线程的属性。一般取默认属性,即该参数取NULL
start_routine, 该线程的处理函数
该函数的返回类型和参数类型都是void*
arg, 线程处理函数start_routine的参数
*/
for (n = 0; n < tp->threads; n++) {
// 参数:tid , attr , 线程启动后执行函数 , thread_pool_cycle的参数
// thread_pool_cycle:线程池的主循环
err = pthread_create(&tid, &attr, thread_pool_cycle, tp);
if (err) {
fprintf(stderr, "pthread_create() failed, reason: %s\n",strerror(errno));
free(tp);
return NULL;
}
}
(void) pthread_attr_destroy(&attr);// 销毁设置属性
return tp;
}
// 线程池的销毁
void thread_pool_destroy(thread_pool_t *tp)
{
/* 我要干掉你这个线程就让你执行一个自杀函数就行了*/
uint_t n;
thread_task_t task;
volatile uint_t lock; // 无符号整形数
memset(&task,'\0', sizeof(thread_task_t));
task.handler = thread_pool_exit_handler;// 函数执行自杀
task.ctx = (void *) &lock;
// 自杀所有线程
for (n = 0; n < tp->threads; n++) {
lock = 1;
// 向线程池中投递任务
if (thread_task_post(tp, &task) != OK) {
return;
}
while (lock) {
sched_yield();// 线程放弃CPU的优先权
}
// 当自杀任务被执行完毕后,就会把这个任务从线程池中移除,详情参见thread_pool_cycle的源码实现
//task.event.active = 0;
}
(void) thread_cond_destroy(&tp->cond);
(void) thread_mutex_destroy(&tp->mtx);
free(tp);
}
// 函数执行自杀
static void
thread_pool_exit_handler(void *data)
{
uint_t *lock = (uint_t *)data;
*lock = 0;
pthread_exit(0);
}
// size:任务函数所要带的参数大小
thread_task_t *
thread_task_alloc(size_t size)
{
thread_task_t *task;
// 一起分配内存:任务结构体 , 任务大小
task = (thread_task_t *)calloc(1,sizeof(thread_task_t) + size);
if (task == NULL) {
return NULL;
}
// 相当于task移动了sizeof(thread_task_t)个字节
// 即被内存分成两份第一份放thread_task_t,第二份放ctx
task->ctx = task + 1;
return task;
}
void thread_task_free(thread_task_t * task)
{
if (task) {
free(task);
task = NULL;
}
}
// 往线程池中投递任务
int_t
thread_task_post(thread_pool_t *tp, thread_task_t *task)
{
// 上锁 独立占有线程池结构
if (thread_mutex_lock(&tp->mtx) != OK) {
return ERROR;
}
// 不可超过最大队列
if (tp->waiting >= tp->max_queue) {
// 解锁 ,打印队列已经满了
(void) thread_mutex_unlock(&tp->mtx);
fprintf(stderr,"thread pool \"%s\" queue overflow: %ld tasks waiting\n",
tp->name, tp->waiting);
return ERROR;
}
//task->event.active = 1;
// thread_pool_task_id:是一个全局的静态变量
task->id = thread_pool_task_id++;
task->next = NULL;
// 发送信号 唤醒条件变量锁
if (thread_cond_signal(&tp->cond) != OK) {
(void) thread_mutex_unlock(&tp->mtx);
return ERROR;
}
// 向链表尾部插入任务
*tp->queue.last = task;
tp->queue.last = &task->next;
// 等待任务数量 +1
tp->waiting++;
(void) thread_mutex_unlock(&tp->mtx);
if(debug)fprintf(stderr,"task #%lu added to thread pool \"%s\"\n",
task->id, tp->name);
return OK;
}
static void *
thread_pool_cycle(void *data)
{
thread_pool_t *tp = (thread_pool_t *)data; // 拿到线程池结构体
int err;
thread_task_t *task;
if(debug)fprintf(stderr,"thread in pool \"%s\" started\n", tp->name);
for ( ;; ) {
// 上锁 自己独占资源,因为多线程一起访问的话很容易出错,比如多个线程对链表操作
if (thread_mutex_lock(&tp->mtx) != OK) {
return NULL;
}
// 上锁后,线程会拿到一个任务
tp->waiting--;
// 判断池子中有没有任务
while (tp->queue.first == NULL) {
//如果没有任务
// thread_cond_wait :解锁-阻塞等待信号-信号来了-加锁-执行任务
//当有任务来的时候,就会被唤醒 条件锁
if (thread_cond_wait(&tp->cond, &tp->mtx)
!= OK)
{
// 函数执行错误就解锁
(void) thread_mutex_unlock(&tp->mtx);
return NULL;
}
}
// 拿到队列中的任务后,把队列中这个任务去除即first指向下一个任务
task = tp->queue.first;
tp->queue.first = task->next;
if (tp->queue.first == NULL) {
tp->queue.last = &tp->queue.first;
}
//解锁
if (thread_mutex_unlock(&tp->mtx) != OK) {
return NULL;
}
if(debug) fprintf(stderr,"run task #%lu in thread pool \"%s\"\n",
task->id, tp->name);
task->handler(task->ctx);// 处理任务
if(debug) fprintf(stderr,"complete task #%lu in thread pool \"%s\"\n",task->id, tp->name);
task->next = NULL;
//释放task
// free(task); // 一次性把thread_task_t和处理任务的参数一起释放了
thread_task_free(task);
//notify
}
}
// 设置默认属性
static int_t
thread_pool_init_default(thread_pool_t *tpp, char *name)
{
if(tpp)
{
tpp->threads = DEFAULT_THREADS_NUM;// 设置线程数
tpp->max_queue = DEFAULT_QUEUE_NUM;// 最大队列数
tpp->name = strdup(name?name:"default");// 设置线程池名字
if(debug)fprintf(stderr,
"thread_pool_init, name: %s ,threads: %lu max_queue: %ld\n",
tpp->name, tpp->threads, tpp->max_queue);
return OK;
}
return ERROR;
}