1. 为什么使用线程池
某类任务特别耗时的时候,严重影响该线程处理其他任务
特别耗时:cpu计算,磁盘io,网络io 都有可能的
这时候可以将这些耗时的操作丢到线程池中,让线程池中的线程异步的执行该任务
作用:复用线程资源; 减少线程创建和销毁的开销; 可异步处理生产者线程的任务; 减少了多个任务(不是一个任务)的执行时间;
2. 线程池的思想
使用的是预先准备好(初始化好的)一些线程,进行耗时任务的处理
主要是一个生产消费模型:
生产者: 添加耗时任务
消费者:线程池
队列: 记录耗时任务(包含 任务的上下文,任务的处理函数)
3. 基本的线程池的实现
a. 接口的设计:
创建线程池的接口:思考 线程的数量,队列的长度
销毁线程池的接口:思考 线程池标记位退出,通知所有线程退出
生产者抛入任务的接口: 思考 构造任务,放入队列,唤醒线程池中的线程
typedef struct thread_pool_t thread_pool_t;
typedef void (*handler_pt) (void *);
//线程池的构建
thread_pool_t *thread_pool_create(int thrd_count, int queue_size);
// 生产者抛入任务
int thread_pool_post(thread_pool_t *pool, handler_pt func, void *arg);
// 线程池的销毁
int thread_pool_destroy(thread_pool_t *pool);
// 通知关闭等待所有线程关闭接口
int wait_all_done(thread_pool_t *pool);
b. 具体结构的设计:
typedef void (*handler_pt) (void*);
//任务结构
typedef struct task_t {
handler_pt func; //任务回调函数
void * arg; // 回调函数参数
} task_t;
// 队列的结构
// 这里使用的是数组进行存储任务
// 也可以换成链表进行存储
typedef struct task_queue_t {
uint32_t head; // 当前消费的任务位置
uint32_t tail; // 当前插入任务的位置
uint32_t count;// 记录任务的个数
task_t *queue; // 具体的任务数组
} task_queue_t;
// 线程池的结构
struct thread_pool_t {
pthread_mutex_t mutex; //使用的互斥锁,对队列进行加锁解锁
pthread_cond_t condition; //条件变量,方便队列中任务的通知
pthread_t *threads; //记录线程
task_queue_t task_queue; // 队列
int closed; //标记位,记录是否关闭线程池
int started; // 当前运行的线程数
int thrd_count; // 线程池中线程的数量
int queue_size; // 队列的长度
};
c. 具体代码的实现
static void * thread_worker(void *thrd_pool);
static void thread_pool_free(thread_pool_t *pool);
thread_pool_t *
thread_pool_create(int thrd_count, int queue_size) {
thread_pool_t *pool;
if (thrd_count <= 0 || queue_size <= 0) {
return NULL;
}
pool = (thread_pool_t*) malloc(sizeof(*pool));
if (pool == NULL) {
return NULL;
}
pool->thrd_count = 0;
pool->queue_size = queue_size;
pool->task_queue.head = 0;
pool->task_queue.tail = 0;
pool->task_queue.count = 0;
pool->started = pool->closed = 0;
pool->task_queue.queue = (task_t*)malloc(sizeof(task_t)*queue_size);
if (pool->task_queue.queue == NULL) {
// TODO: free pool
return NULL;
}
pool->threads = (pthread_t*) malloc(sizeof(pthread_t) * thrd_count);
if (pool->threads == NULL) {
// TODO: free pool
return NULL;
}
int i = 0;
for (; i < thrd_count; i++) {
if (pthread_create(&(pool->threads[i]), NULL, thread_worker, (void*)pool) != 0) {
// TODO: free pool
return NULL;
}
pool->thrd_count++;
pool->started++;
}
return pool;
}
int
thread_pool_post(thread_pool_t *pool, handler_pt func, void *arg) {
if (pool == NULL || func == NULL) {
return -1;
}
task_queue_t *task_queue = &(pool->task_queue);
if (pthread_mutex_lock(&(pool->mutex)) != 0) {
return -2;
}
if (pool->closed) {
pthread_mutex_unlock(&(pool->mutex));
return -3;
}
if (task_queue->count == pool->queue_size) {
pthread_mutex_unlock(&(pool->mutex));
return -4;
}
task_queue->queue[task_queue->tail].func = func;
task_queue->queue[task_queue->tail].arg = arg;
task_queue->tail = (task_queue->tail + 1) % pool->queue_size;
task_queue->count++;
if (pthread_cond_signal(&(pool->condition)) != 0) {
pthread_mutex_unlock(&(pool->mutex));
return -5;
}
pthread_mutex_unlock(&(pool->mutex));
return 0;
}
static void
thread_pool_free(thread_pool_t *pool) {
if (pool == NULL || pool->started > 0) {
return;
}
if (pool->threads) {
free(pool->threads);
pool->threads = NULL;
pthread_mutex_lock(&(pool->mutex));
pthread_mutex_destroy(&pool->mutex);
pthread_cond_destroy(&pool->condition);
}
if (pool->task_queue.queue) {
free(pool->task_queue.queue);
pool->task_queue.queue = NULL;
}
free(pool);
}
int
wait_all_done(thread_pool_t *pool) {
int i, ret=0;
for (i=0; i < pool->thrd_count; i++) {
if (pthread_join(pool->threads[i], NULL) != 0) {
ret=1;
}
}
return ret;
}
int
thread_pool_destroy(thread_pool_t *pool) {
if (pool == NULL) {
return -1;
}
if (pthread_mutex_lock(&(pool->mutex)) != 0) {
return -2;
}
if (pool->closed) {
thread_pool_free(pool);
return -3;
}
pool->closed = 1;
if (pthread_cond_broadcast(&(pool->condition)) != 0 ||
pthread_mutex_unlock(&(pool->mutex)) != 0) {
thread_pool_free(pool);
return -4;
}
wait_all_done(pool);
thread_pool_free(pool);
return 0;
}
static void *
thread_worker(void *thrd_pool) {
thread_pool_t *pool = (thread_pool_t*)thrd_pool;
task_queue_t *que;
task_t task;
for (;;) {
pthread_mutex_lock(&(pool->mutex));
que = &pool->task_queue;
// 虚假唤醒 linux pthread_cond_signal
// linux 可能被信号唤醒
// 业务逻辑不严谨,被其他线程抢了该任务
while (que->count == 0 && pool->closed == 0) {
// pthread_mutex_unlock(&(pool->mutex))
// 阻塞在 condition
// ===================================
// 解除阻塞
// pthread_mutex_lock(&(pool->mutex));
pthread_cond_wait(&(pool->condition), &(pool->mutex));
}
if (pool->closed == 1) break;
task = que->queue[que->head];
que->head = (que->head + 1) % pool->queue_size;
que->count--;
pthread_mutex_unlock(&(pool->mutex));
(*(task.func))(task.arg);
}
pool->started--;
pthread_mutex_unlock(&(pool->mutex));
pthread_exit(NULL);
return NULL;
}
4.线程池在中间件和框架中的实现
a. nginx中线程池的实现
作用: 处理文件缓存
模型图:
结构设计:
//在中添加到了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中执行
};
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配置在配置文件中的行号
};
接口实现:
ngx_thread_pool_t *ngx_thread_pool_add(ngx_conf_t *cf, ngx_str_t *name);
ngx_thread_pool_t *ngx_thread_pool_get(ngx_cycle_t *cycle, ngx_str_t *name);
ngx_thread_task_t *ngx_thread_task_alloc(ngx_pool_t *pool, size_t size);
ngx_int_t ngx_thread_task_post(ngx_thread_pool_t *tp, ngx_thread_task_t *task);
其中ngx_thread_task_post 是将任务添加到任务等待队列中,生产者:
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;
// 任务添加后的通知 条件变量的signal
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;
}
消费者:
//创建线程池所需的基础结构
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_done
ngx_thread_pool_queue_init(&ngx_thread_pool_done);
tpp = tcf->pools.elts;
for (i = 0; i < tcf->pools.nelts; i++) {
// 初始化各个线程池tpp[i]存储这各个线程池
// 从配置文件中读取ngx_thread_pool_init_conf 进行设置
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;
// 通过ngx_notify 进行通知主线程 完成队列中有内容
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;
}
// 设置线程属性是PTHREAD_CREATE_DETACHED,不用主线程进行join资源回收,自行处理了
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++) {
// ngx_thread_pool_cycle 消费者核心
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;
}
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循环进行虚假唤醒的检测
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;
}
}
// 从first位置拿数据
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
}
b. redis中线程池的实现
作用:读写的io的处理,以及数据包的解析压缩
tip:Redis 6.0 版本以前的线程模型为典型的 单 Reactor 单线程 ,但是其多线程模型却与标准的 单 Reactor 多线程 不太相同,区别在于 Redis 多线程模型不是把业务逻辑处理交给子线程,而是把对网络数据的读写交给子线程处理,业务逻辑仍然由主线程完成。
read decode 和 encode send 是redis的多线程所处理的事情。
模型图:
源码分析:
IO线程初始化:
在Redis Server启动函数main(server.c文件)中初始化服务之后,又调用了InitServerLast函数 :
int main(int argc, char **argv) {
// ...
// 初始化服务
initServer();
// ...
// InitServerLast
InitServerLast();
// ...
// 事件循环
aeMain(server.el);
// ...
}
InitServerLast函数在server.c文件中,它调用了initThreadedIO函数对IO线程初始化:
void InitServerLast() {
bioInit();
// 初始化IO线程
initThreadedIO();
set_jemalloc_bg_thread(server.jemalloc_bg_thread);
server.initial_memory_usage = zmalloc_used_memory();
}
initThreadedIO的实现在networking.c文件中:
函数负责初始化 IO 线程的数据结构,是整个 IO 线程初始化的核心,其涉及的重要属性及步骤如下
重要属性:
io_threads_active 标志 IO 线程的激活状态,默认为 0 ,也就是非激活态
server.io_threads_num 配置文件中配置的 IO 线程数量,为 1 则只有主线程处理 IO ,不需要再创建线程,大于 IO_THREADS_MAX_NUM(128)则认为配置异常,退出程序
io_threads_list 数组,数组元素为 list 列表,每个 IO 线程需要处理的 client 都放在数组对应下标的 list 中
io_threads_pending 数组,记录每个 IO 线程待处理的 client 数量
处理步骤:调用函数 listCreate() 为每个 IO 线程创建任务列表
初始化 IO 线程互斥对象,初始化 IO 线程当前未处理的任务数量为 0
pthread_create() 函数设置 IO 线程运行时处理的函数为 IOThreadMain()
/* 初始化线程 */
void initThreadedIO(void) {
server.io_threads_active = 0; /* 初始化线程活跃状态为0,表示未激活IO多线程 */
/* 如果IO线程数为1,直接返回即可 */
if (server.io_threads_num == 1) return;
/* 如果IO线程数超过了最大限制,打印错误,停止redis服务 */
if (server.io_threads_num > IO_THREADS_MAX_NUM) {
serverLog(LL_WARNING,"Fatal: too many I/O threads configured. "
"The maximum number is %d.", IO_THREADS_MAX_NUM);
exit(1);
}
/* 根据线程数设置创建线程 */
for (int i = 0; i < server.io_threads_num; i++) {
/* 创建List */
io_threads_list[i] = listCreate();
if (i == 0) continue; /* 下标为0的存储的是主线程 */
pthread_t tid;
pthread_mutex_init(&io_threads_mutex[i],NULL);
// 初始化待处理的客户端数量为0
setIOPendingCount(i, 0);
pthread_mutex_lock(&io_threads_mutex[i]); /* Thread will be stopped. */
// 创建线程, 线程的运行函数为IOThreadMain
if (pthread_create(&tid,NULL,IOThreadMain,(void*)(long)i) != 0) {
serverLog(LL_WARNING,"Fatal: Can't initialize IO thread.");
exit(1);
}
/* 将创建的线程加入io_threads线程组中*/
io_threads[i] = tid;
}
}
// setIOPendingCount在networking.c文件
static inline void setIOPendingCount(int i, unsigned long count) {
// 设置io_threads_pending[i]的值为count
atomicSetWithSync(io_threads_pending[i], count);
}
//其中
/* io_threads_list存储每个线程要处理的客户端 */
list *io_threads_list[IO_THREADS_MAX_NUM];
/* 存储创建的线程*/
pthread_t io_threads[IO_THREADS_MAX_NUM];
/* 存储每个线程要等待处理的客户端个数 */
redisAtomic unsigned long io_threads_pending[IO_THREADS_MAX_NUM];
#define IO_THREADS_MAX_NUM 128
接下来看一下IO线程运行函数IOThreadMain在networking.c文件中,其处理主要分为以下几个步骤
开启空循环扫描 io_threads_pending 数组,如果找到属于当前线程的那个下标在数组中的值不为 0 则跳出扫描
再次检查当前线程待处理客户端的数量,如果为 0 ,则当前线程停止运行
从 io_threads_list 列表数组中取出当前线程待处理的 client 的列表,根据 io_threads_op 全局标志位决定对这些 client 做对应的处理,比如 IO_THREADS_OP_READ 读操作则调用 readQueryFromClient() 函数继续处理,IO_THREADS_OP_WRITE 写操作则调用writeToClient()函数进行处理
处理完毕后,清空 io_threads_list 列表数组中当前线程待处理的 client 的列表,并将 io_threads_pending 对应下标值置为 0,主线程利用该数组即可知道 IO 线程是否执行完所有读写任务
具体代码如下:
void *IOThreadMain(void *myid) {
/* myid是线程ID,从0开始,到 server.iothreads_num-1,0号线程存储的是主线程 */
long id = (unsigned long)myid;
char thdname[16];
snprintf(thdname, sizeof(thdname), "io_thd_%ld", id);
redis_set_thread_title(thdname);
redisSetCpuAffinity(server.server_cpulist);
makeThreadKillable();
// 循环
while(1) {
for (int j = 0; j < 1000000; j++) {
if (getIOPendingCount(id) != 0) break;
}
/* Give the main thread a chance to stop this thread. */
if (getIOPendingCount(id) == 0) {
pthread_mutex_lock(&io_threads_mutex[id]);
pthread_mutex_unlock(&io_threads_mutex[id]);
continue;
}
serverAssert(getIOPendingCount(id) != 0);
listIter li;
listNode *ln;
// 获取每一个IO线程要处理的客户端,将其放入到迭代器li,这里的id指的线程id
listRewind(io_threads_list[id],&li);
// 遍历列表
while((ln = listNext(&li))) {
// 获取每一个待处理的客户端
client *c = listNodeValue(ln);
// 如果是写事件
if (io_threads_op == IO_THREADS_OP_WRITE) {
// 调用writeToClient处理
writeToClient(c,0);
} else if (io_threads_op == IO_THREADS_OP_READ) {
// 如果是读事件,调用readQueryFromClient
readQueryFromClient(c->conn);
} else {
serverPanic("io_threads_op value is unknown");
}
}
listEmpty(io_threads_list[id]);
// 处理完毕后,io_threads_pending数组中对应的数量设置为0,表示所有客户端已处理完毕
setIOPendingCount(id, 0);
}
}
延迟读写操作:
Redis在处理客户端读事件和写事件时会根据一定条件推迟客户端的读取操作或者往客户端写数据操作,将待处理的读客户端和待处理的写客户端分别加入到全局变量server的clients_pending_read和clients_pending_write列表中,全局变量server对应的结构体为redisServer:
全局变量server定义,在server.c文件:
/* 全局变量server */
struct redisServer server;
redisServer的结构体定义在server.h中:
struct redisServer {
list *clients_pending_write; /* list类型,记录延迟写回数据的客户端 */
list *clients_pending_read; /* list类型,记录延迟读取数据的客户端*/
// 省略...
}
推迟客户端读操作
readQueryFromClient
readQueryFromClient主要处理从客户端读取数据,在networking.c中实现,里面调用了postponeClientRead函数判断是否需要推迟客户端的读取操作 :
void readQueryFromClient(connection *conn) {
client *c = connGetPrivateData(conn);
int nread, readlen;
size_t qblen;
/* 判断是否需要推迟客户端的读取操作 */
if (postponeClientRead(c)) return;
// 省略...
// 处理数据执行命令
processInputBuffer(c);
}
postponeClientRead
postponeClientRead函数用于判断是否延迟从客户端读取数据,包含四个条件:
- server.io_threads_active为1,表示激活了IO多线程
- server.io_threads_do_reads为1,表示IO多线程可以延迟执行客户端的读取操作,在配置文件中定义,可以通过修改配置文件来开启延迟读取客户端数据
- ProcessingEventsWhileBlocked值为0,processEventsWhileBlokced函数在执行时会将ProcessingEventsWhileBlocked的值置为1,执行完毕后置为0,Redis在读取RDB或者AOF文件时会调用processEventsWhileBlokced函数,为了避免读取RDB或AOF文件时阻塞无法及时处理请求,processEventsWhileBlokced函数在执行时不能推迟客户端数据读取。
- 客户端的现有标识不能有CLIENT_MASTER、CLIENT_SLAVE、CLIENT_PENDING_READ、CLIENT_BLOCKED等状态
- CLIENT_MASTER、CLIENT_SLAVE表示是用于主从复制的客户端
- CLIENT_PENDING_READ表示客户端本身已经是推迟读取状态
- CLIENT_BLOCKED表示客户端是阻塞状态
满足以上四个条件时将推迟从客户端读取数据,会将客户端标识置为CLIENT_PENDING_READ延迟读状态,并将待读取数据的客户端client加入到server.clients_pending_read中。
int postponeClientRead(client *c) {
if (server.io_threads_active &&
server.io_threads_do_reads &&
!ProcessingEventsWhileBlocked &&
!(c->flags & (CLIENT_MASTER|CLIENT_SLAVE|CLIENT_PENDING_READ|CLIENT_BLOCKED)))
{
c->flags |= CLIENT_PENDING_READ;
// 将客户端加入到clients_pending_read链表中
listAddNodeHead(server.clients_pending_read,c);
return 1;
} else {
return 0;
}
}
推迟客户端写操作
在往客户端写数据的addReply(networking.c)函数中,调用了prepareClientToWrite判断是否准备往客户端写数据:
void addReply(client *c, robj *obj) {
// 调用prepareClientToWrite往客户端写数据
if (prepareClientToWrite(c) != C_OK) return;
if (sdsEncodedObject(obj)) {
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != C_OK)
_addReplyProtoToList(c,obj->ptr,sdslen(obj->ptr));
} else if (obj->encoding == OBJ_ENCODING_INT) {
char buf[32];
size_t len = ll2string(buf,sizeof(buf),(long)obj->ptr);
if (_addReplyToBuffer(c,buf,len) != C_OK)
_addReplyProtoToList(c,buf,len);
} else {
serverPanic("Wrong obj->encoding in addReply()");
}
}
prepareClientToWrite
prepareClientToWrite(networking.c)中,首先对客户端标识状态进行了一系列的判断,然后调用了clientHasPendingReplies函数判断输出缓冲区是否有还有数据等待写回到客户端,如果没有,判断客户端的标识是否是CLIENT_PENDING_READ已延迟读,如果不是CLIENT_PENDING_READ状态,调用clientInstallWriteHandler处理:
int prepareClientToWrite(client *c) {
if (c->flags & (CLIENT_LUA|CLIENT_MODULE)) return C_OK;
if (c->flags & CLIENT_CLOSE_ASAP) return C_ERR;
if (c->flags & (CLIENT_REPLY_OFF|CLIENT_REPLY_SKIP)) return C_ERR;
if ((c->flags & CLIENT_MASTER) &&
!(c->flags & CLIENT_MASTER_FORCE_REPLY)) return C_ERR;
if (!c->conn) return C_ERR;
/*
* 如果缓冲区的数据都已写回到客户端并且客户端标识不是推迟读状态
*/
if (!clientHasPendingReplies(c) && !(c->flags & CLIENT_PENDING_READ))
clientInstallWriteHandler(c);// 调用clientInstallWriteHandler
return C_OK;
}
clientInstallWriteHandler
clientInstallWriteHandler(networking.c)函数中对是否推迟客户端写操作进行了判断:
- 客户端标识不是CLIENT_PENDING_WRITE,对应条件为!(c->flags & CLIENT_PENDING_WRITE),表示客户端本身不是推迟写状态
- 客户端未在进行主从复制(对应条件为c->replstate == REPL_STATE_NONE) 或者 客户端是主从复制的从节点,但全量复制的 RDB 文件已经传输完成,客户端可以接收请求(对应条件 !c->repl_put_online_on_ack))
满足以上两个条件时将推迟客户端写操作,将客户端的标识置为延迟写CLIENT_PENDING_WRITE状态,并将客户端加入到待写回的列表server.clients_pending_write中。
void clientInstallWriteHandler(client *c) {
/* 如果客户端的标识不是推迟写状态,并且客户端未在进行主从复制或者客户端是主从复制的从节点并能接收请求 */
if (!(c->flags & CLIENT_PENDING_WRITE) &&
(c->replstate == REPL_STATE_NONE ||
(c->replstate == SLAVE_STATE_ONLINE && !c->repl_put_online_on_ack)))
{
/* 将客户端的标识置为延迟写 */
c->flags |= CLIENT_PENDING_WRITE;
// 将客户端加入到待写回的列表clients_pending_write中
listAddNodeHead(server.clients_pending_write,c);
}
}
IO线程的分配
上面我们已经知道了IO线程的初始化、IO线程的运行函数IOThreadMain主要处理逻辑,以及延迟读写的客户端是何时分别加入到server全局变量的clients_pending_read和clients_pending_write中的,接下来去看下时何时为客户端分配线程。
即便用户配置了 IO 多线程,Redis 在实际的处理中也不一定就会启用多线程处理网络 IO,而是根据系统状态动态地调整。上文已经提到 io_threads_active 标志 IO 线程的激活状态,默认 IO 线程为未激活的状态,追踪该变量即可知道 IO 线程启动的位置。我们先看看 server.c文件中的beforeSleep() 函数, beforeSleep() 函数实际是在每次事件处理之前调用的函数,其内部处理的逻辑比较多,本文关注的主要有以下几个:
void beforeSleep(struct aeEventLoop *eventLoop) {
UNUSED(eventLoop);
// 省略...
handleBlockedClientsTimeout();
/* 调用了handleClientsWithPendingReadsUsingThreads为延迟读客户端分配线程 */
handleClientsWithPendingReadsUsingThreads();
// 省略...
/* 调用了handleClientsWithPendingWritesUsingThreads为延迟写客户端分配线程 */
handleClientsWithPendingWritesUsingThreads();
// 省略...
}
在networking.c文件中handleClientsWithPendingWritesUsingThreads()
函数源码如下,其处理流程较为重要:
首先查看数组 server.clients_pending_write 长度是否为 0,为 0 则没有在等待响应的客户端,不需要使用 IO 线程处理。 Redis 使用了server.clients_pending_write 全局数组来存放等待响应的客户端,另一个数组 server.clients_pending_read 则存放等待读取数据的客户端
server.io_threads_num 配置的线程数为 1 或者 stopThreadedIOIfNeeded() 函数返回 true,说明用户没有配置多线程 IO 或者系统动态判断当前不需要使用多线程 IO,则直接调用 handleClientsWithPendingWrites() 函数完成客户端响应
接下来判断 io_threads_active 全局变量不为 1,说明 IO 线程还没有激活,则调用 startThreadedIO() 函数启动 IO 线程
将数组 server.clients_pending_write 中存放的待响应客户端按照server.io_threads_num 取余分配到各个 IO 线程的任务列表 io_threads_list[target_id] 中
设置 io_threads_op 全局 IO 操作标志为 IO_THREADS_OP_WRITE,则 IO 线程都处理写任务,并更新io_threads_pending 数组。这部分暂不展开,下文详细分析
主线程空循环,等待 IO 线程处理任务完毕。这部分逻辑主要靠io_threads_pending 数组记录每个 IO 线程待处理的 client 数量来判断,如果各个 IO 线程待处理的 client 数量相加为 0,则任务处理完毕,主线程跳出循环
最后如果还有待处理的客户端则继续处理,处理完毕清空 server.clients_pending_write 数组
int handleClientsWithPendingWritesUsingThreads(void) {
int processed = listLength(server.clients_pending_write);
if (processed == 0) return 0;
if (server.io_threads_num == 1 || stopThreadedIOIfNeeded()) {
return handleClientsWithPendingWrites();
}
// 在这里开启线程池的逻辑
if (!server.io_threads_active) startThreadedIO();
listIter li;
listNode *ln;
// 获取待写回客户端列表clients_pending_write加入到迭代链表中
listRewind(server.clients_pending_write,&li);
int item_id = 0;
// 遍历待写的客户端
while((ln = listNext(&li))) {
client *c = listNodeValue(ln);
c->flags &= ~CLIENT_PENDING_WRITE;
if (c->flags & CLIENT_CLOSE_ASAP) {
listDelNode(server.clients_pending_write, ln);
continue;
}
// 根据线程数取模,轮询分配线程
int target_id = item_id % server.io_threads_num;
// 分配线程,加入到对应线程的io_threads_list
listAddNodeTail(io_threads_list[target_id],c);
item_id++;
}
/* 将io_threads_op线程操作状态置为写操作 */
io_threads_op = IO_THREADS_OP_WRITE;
for (int j = 1; j < server.io_threads_num; j++) {
int count = listLength(io_threads_list[j]);
// 设置每个线程需要处理的客户端个数
setIOPendingCount(j, count);
}
/* 获取io_threads_list[0]中待处理的客户端列表,io_threads_list[0]存储的是主线程的数据*/
/* handleClientsWithPendingWritesUsingThreads函数的执行者刚好就是主线程,所以让主线程处理io_threads_list[0]中的数据*/
listRewind(io_threads_list[0],&li);
while((ln = listNext(&li))) {
client *c = listNodeValue(ln);
// 调用writeToClient往客户写数据
writeToClient(c,0);
}
listEmpty(io_threads_list[0]);
/* 等待其他线程处理完毕 */
while(1) {
unsigned long pending = 0;
for (int j = 1; j < server.io_threads_num; j++)
pending += getIOPendingCount(j);
if (pending == 0) break;
}
/* 再次获取server.clients_pending_read所有待写的客户端*/
listRewind(server.clients_pending_write,&li);
// 遍历
while((ln = listNext(&li))) {
client *c = listNodeValue(ln);
/* 如果缓冲区数据未全部写回调用connSetWriteHandler注册可写事件,回调函数为sendReplyToClient*/
if (clientHasPendingReplies(c) &&
connSetWriteHandler(c->conn, sendReplyToClient) == AE_ERR)
{
freeClientAsync(c);
}
}
// 清空clients_pending_write
listEmpty(server.clients_pending_write);
server.stat_io_writes_processed += processed;
return processed;
}
从server.clients_pending_write获取延迟写操作的客户端,将其加入到迭代列表
遍历延迟写操作的客户端列表,获取每一个待处理的客户端client,使用取模的方式轮询为每一个客户端分配线程,然后将客户端加入到该线程待处理的客户端列表中,此时客户端已分配到线程,在线程的运行函数IOThreadMain会处待写回数据的客户端
将io_threads_op线程操作状态置为写操作
遍历线程数,获取每一个线程要处理的客户端个数,将其设置到线程对应的io_threads_pending[j]中,io_threads_pending数组中记录了每个线程等待处理的客户端个数
获取io_threads_list[0]中待处理的客户端列表,io_threads_list[0]存储的是主线程的数据,因为当前执行handleClientsWithPendingWritesUsingThreads函数的线程正是主线程,所以让主线程来处理io_threads_list[0]中存放的待处理客户端
主线程遍历io_threads_list[0]中每一个待处理的客户端,调用writeToClient往客户端写数据
主线程开启一个while(1)循环等待其他IO线程处理完毕
主线程开启while循环,循环条件是server.clients_pending_write列表的长度不为0,遍历clients_pending_write中待处理的写客户端:
(1)调用listNodeValue获取待处理的客户端client
(2)判断缓冲区数据是否全部写回到客户端,如果未全部写回调用connSetWriteHandler向内核注册写事件监听,回调函数为sendReplyToClient,待事件循环流程再次执行时,注册的可写事件会通过回调函数sendReplyToClient 处理,把缓冲区中的数据写回客户端。
调用listEmpty函数清空server.clients_pending_write列表
在networking.c文件中startThreadedIO()
函数较为简单,可以看到其逻辑主要为以下几步:
- 首先将创建 IO 线程时锁住的互斥对象解锁,也就是使 IO 线程得以运行
- 将全局变量
io_threads_active
赋值为 1 ,标志 IO 线程已经激活
void startThreadedIO(void) {
if (tio_debug) { printf("S"); fflush(stdout); }
if (tio_debug) printf("--- STARTING THREADED IO ---\n");
serverAssert(io_threads_active == 0);
for (int j = 1; j < server.io_threads_num; j++)
pthread_mutex_unlock(&io_threads_mutex[j]);
io_threads_active = 1;
}
connSetWriteHandler
connSetWriteHandler函数在connection.c文件中,它通过set_write_handler注册了写handler,set_write_handler对应的是connSocketSetWriteHandler函数,所以connSetWriteHandler会被映射为connSocketSetWriteHandler,connSocketSetWriteHandler函数调用了aeCreateFileEvent向内核中注册可写事件监听,上面可知回调函数为sendReplyToClient ,等事件循环流程再次执行时,handleClientsWithPendingWritesUsingThreads 函数注册的可写事件会通过回调函数sendReplyToClient 处理,把缓冲区中的数据写回客户端。
ConnectionType CT_Socket = {
.ae_handler = connSocketEventHandler,
.close = connSocketClose,
.write = connSocketWrite,
.read = connSocketRead,
.accept = connSocketAccept,
.connect = connSocketConnect,
.set_write_handler = connSocketSetWriteHandler, // set_write_handler对应connSocketSetWriteHandler函数
.set_read_handler = connSocketSetReadHandler,
.get_last_error = connSocketGetLastError,
.blocking_connect = connSocketBlockingConnect,
.sync_write = connSocketSyncWrite,
.sync_read = connSocketSyncRead,
.sync_readline = connSocketSyncReadLine,
.get_type = connSocketGetType
};
/*
* connSetWriteHandler
*/
static inline int connSetWriteHandler(connection *conn, ConnectionCallbackFunc func) {
// 注册写handler, set_write_handler对应的是connSocketSetWriteHandler函数
return conn->type->set_write_handler(conn, func, 0);
}
/*
* connSocketSetWriteHandler注册写事件
*/
static int connSocketSetWriteHandler(connection *conn, ConnectionCallbackFunc func, int barrier) {
if (func == conn->write_handler) return C_OK;
conn->write_handler = func;
if (barrier)
conn->flags |= CONN_FLAG_WRITE_BARRIER;
else
conn->flags &= ~CONN_FLAG_WRITE_BARRIER;
if (!conn->write_handler)
aeDeleteFileEvent(server.el,conn->fd,AE_WRITABLE);
else
if (aeCreateFileEvent(server.el,conn->fd,AE_WRITABLE, // 向内核注册写事件
conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
return C_OK;
}
在networking.c文件中
handleClientsWithPendingReadsUsingThreads():
主要逻辑如下:
-
从server.clients_pending_read获取延迟读取操作的客户端,将其加入到迭代列表
-
遍历延迟读操作的客户端列表,获取每一个待处理的客户端client,item_id表示每个客户端的序号,从0开始,每处理一个客户端就增1,用序号对线程数server.io_threads_num取模,得到一个target_id,客户端会被加入到io_threads_list[target_id]对应的列表中,也就是使用取模的方式轮询为每一个客户端分配对应线程,然后将客户端加入到该线程待处理的客户端列表中,此时客户端已分配到线程,在线程的运行函数IOThreadMain会调用readQueryFromClient处理客户端数据,需要注意多线程只是从客户端数据读取数据解析命令,并不会执行命令,在processInputBuffer中可以看到在IO多线程下只会将flags状态标记为CLIENT_PENDING_COMMAND,不会执行processCommandAndResetClient函数:
void processInputBuffer(client *c) { while(c->qb_pos < sdslen(c->querybuf)) { // 省略... if (c->argc == 0) { resetClient(c); } else { /* 在IO多线程情况下不能在这里执行命令,所以在这里将client标记为CLIENT_PENDING_COMMAND然后返回,等待主线程同步执行命令 */ if (c->flags & CLIENT_PENDING_READ) { c->flags |= CLIENT_PENDING_COMMAND; break; } /* 准备执行命令 */ if (processCommandAndResetClient(c) == C_ERR) { return; } } } // 省略... }
-
将io_threads_op线程操作状态置为读操作
-
遍历线程数,获取每一个线程要处理的客户端个数,将其设置到线程对应的io_threads_pending[j]中,io_threads_pending数组中记录了每个线程等待处理的客户端个数
-
获取io_threads_list[0]中待处理的客户端列表,io_threads_list[0]存储的是主线程的数据,因为当前执行handleClientsWithPendingReadsUsingThreads函数的线程正是主线程,所以让主线程来处理io_threads_list[0]中存放的待处理客户端
-
主线程遍历io_threads_list[0]中每一个待处理的客户端,调用readQueryFromClient处理,从客户端读取数据
-
主线程开启一个while(1)循环等待其他IO线程处理完毕,结束条件是pending为0,pending记录了所有线程要处理的客户端数量总和,在前面IOThreadMain函数中可以看到线程在处理完毕之后会将对应io_threads_pending数组中记录的个数置为0,当pending为0表示所有的线程都已将各自复制的客户端数据处理完毕
-
主线程开启while循环准备执行客户端命令(注意这里才开始执行命令,多线程只负责解析不负责执行),循环条件是server.clients_pending_read列表的长度不为0,主线程需要保证客户端的请求顺序,所从clients_pending_read列表中的第一个元素开始向后遍历:
(1)调用listNodeValue获取列表中的元素,也就是待处理的客户端client
(2)调用listDelNode将获取到的元素从列表删除,因为在第7步中,主线程已经等待其他所有的线程执行完毕,此时所有的线程已经将各自负责的客户端数据处理完成,所以可以将客户端从server.clients_pending_read中移除
(3)调用processPendingCommandsAndResetClient函数判断客户端标识是否是CLIENT_PENDING_COMMAND状态,CLIENT_PENDING_COMMAND状态表示客户端的请求命令已经被IO线程解析(processInputBuffer方法中可以看到状态被标记为CLIENT_PENDING_COMMAND),可以开始执行命令,接着调用processCommandAndResetClient函数执行客户端发送的请求命令
(4)由于客户端输入缓冲区可能有其他的命令未读取,这里调用processInputBuffer处理输入缓冲区数据继续解析命令并执行
int handleClientsWithPendingReadsUsingThreads(void) {
if (!server.io_threads_active || !server.io_threads_do_reads) return 0;
int processed = listLength(server.clients_pending_read);
if (processed == 0) return 0;
listIter li;
listNode *ln;
// 获取待读取的客户端列表clients_pending_read加入到迭代链表中
listRewind(server.clients_pending_read,&li);
int item_id = 0;
// 遍历待读取的客户端
while((ln = listNext(&li))) {
// 获取客户端
client *c = listNodeValue(ln);
// 根据线程数取模,轮询分配线程
int target_id = item_id % server.io_threads_num;
// 分配线程,加入到线程对应的io_threads_list
listAddNodeTail(io_threads_list[target_id],c);
item_id++;
}
/* 将线程的操作状态置为读操作*/
io_threads_op = IO_THREADS_OP_READ;
// 遍历线程数
for (int j = 1; j < server.io_threads_num; j++) {
// 获取每个线程待处理客户端的个数
int count = listLength(io_threads_list[j]);
// 将待处理客户端的个数设置到线程对应的io_threads_pending[j]中,io_threads_pending数组中记录了每个线程要处理的客户端个数
setIOPendingCount(j, count);
}
/* 获取io_threads_list[0]中待处理的客户端列表,io_threads_list[0]存储的是主线程的数据*/
/* handleClientsWithPendingReadsUsingThreads函数的执行者刚好就是主线程,所以让主线程处理io_threads_list[0]中的数据*/
listRewind(io_threads_list[0],&li);
while((ln = listNext(&li))) {
client *c = listNodeValue(ln);
// 调用readQueryFromClient
readQueryFromClient(c->conn);
}
listEmpty(io_threads_list[0]);
/* 等待其他线程处理完毕 */
while(1) {
unsigned long pending = 0;
for (int j = 1; j < server.io_threads_num; j++)
// 获取每一个客户端处理的客户端个数
pending += getIOPendingCount(j);
// 如果为0表示所有线程对应的客户端都处理完毕
if (pending == 0) break;
}
/* 再次判断server.clients_pending_read是否有待处理的客户端*/
while(listLength(server.clients_pending_read)) {
// 获取列表第一个元素
ln = listFirst(server.clients_pending_read);
// 获取客户端
client *c = listNodeValue(ln);
c->flags &= ~CLIENT_PENDING_READ;
// 删除节点
listDelNode(server.clients_pending_read,ln);
serverAssert(!(c->flags & CLIENT_BLOCKED));
// processPendingCommandsAndResetClient函数中会判断客户端标识是否是CLIENT_PENDING_COMMAND状态,如果是调用processCommandAndResetClient函数处理请求命令
if (processPendingCommandsAndResetClient(c) == C_ERR) {
continue;
}
// 由于客户端输入缓冲区可能有其他的命令未读取,这里解析命令并执行
processInputBuffer(c);
if (!(c->flags & CLIENT_PENDING_WRITE) && clientHasPendingReplies(c))
clientInstallWriteHandler(c);
}
/* Update processed count on server */
server.stat_io_reads_processed += processed;
return processed;
}
processPendingCommandsAndResetClient
processPendingCommandsAndResetClient函数在networking.c中,它先判断客户端标识是否是CLIENT_PENDING_COMMAND状态,CLIENT_PENDING_COMMAND状态表示客户端的请求命令已经被IO线程解析,可以被执行,所以如果处于CLIENT_PENDING_COMMAND状态,接下来会调用processCommandAndResetClient函数处理客户端命令,具体是调用processCommand函数执行命令的:
/*processPendingCommandsAndResetClient函数(networking.c中) */
int processPendingCommandsAndResetClient(client *c) {
// 判断客户端标识是否是CLIENT_PENDING_COMMAND
if (c->flags & CLIENT_PENDING_COMMAND) {
// 取消CLIENT_PENDING_COMMAND状态
c->flags &= ~CLIENT_PENDING_COMMAND;
// 调用processCommandAndResetClient执行命令
if (processCommandAndResetClient(c) == C_ERR) {
return C_ERR;
}
}
return C_OK;
}
/* processCommandAndResetClient函数(networking.c中) */
int processCommandAndResetClient(client *c) {
int deadclient = 0;
client *old_client = server.current_client;
server.current_client = c;
// 调用processCommand执行命令
if (processCommand(c) == C_OK) {
commandProcessed(c);
}
if (server.current_client == NULL) deadclient = 1;
server.current_client = old_client;
return deadclient ? C_ERR : C_OK;
}
processCommand
processCommand函数在server.c文件中,它调用了addReply函数将需要返回给客户端的数据先写入缓冲区:
int processCommand(client *c) {
// 省略...
if (!strcasecmp(c->argv[0]->ptr,"quit")) {
// 调用addReply函数将需要返回给客户端的数据先写入缓冲区
addReply(c,shared.ok);
c->flags |= CLIENT_CLOSE_AFTER_REPLY;
return C_ERR;
}
// 省略...
}
c. memcached中线程池的实现
待更新
d.skynet中的线程池的实现
作用:处理读写 io 、数据包解压缩、业务逻辑处理;特别地:当同一个 io 在多个线程处理时,将写 io 转由网络线程处理;
参考文章:
Redis 6.0 源码阅读笔记(2)-Redis 多线程原理_谈谈1974的博客-CSDN博客_redis多线程原理
https://www.cnblogs.com/shanml/p/16192540.html