由于Redis的多线程IO代码较多,故分成了三篇博客讲解
Redis多线程IO源码分析-第一篇_YZF_Kevin的博客-优快云博客
Redis多线程IO源码分析-第二篇_YZF_Kevin的博客-优快云博客
Redis多线程IO源码分析-第二篇_YZF_Kevin的博客-优快云博客
这篇博客我们先分析下redis的多线程IO框架
正式开始
Redis在6.0版本中引入了多线程,以提高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文件中,定义如下(已加注释)
/* 初始化线程 */
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;
}
}
解释下
1. 初始化全局变量 server.io_threads_active线程活跃状态为0,表示未激活IO多线程
2. 对server.io_threads_num的值进行判断,io_threads_num表示配置文件中设置的IO线程数量
如果线程数设置为1,表示不开启多线程直接返回即可
如果线程数超过了IO_THREADS_MAX_NUM设置的最大值(128),则报错并停止redis服务
根据线程数的设置创建线程
3. 初始化io_threads_list,定义如下
/* io_threads_list存储每个线程要处理的客户端 */
list *io_threads_list[IO_THREADS_MAX_NUM];
io_threads_list是一个数组,数组中的每一个元素是一个list,list里面存储每个线程要处理的客户端列表,下标为0的元素也就是io_threads_lis[0]存储的是主线程要处理的客户端列表,这里先调用listCreate创建列表,为io_threads_list[i]初始化
初始化io_threads,保存所有的线程,定义如下
/* 存储创建的线程*/
pthread_t io_threads[IO_THREADS_MAX_NUM];
4. 初始化io_threads_pending,定义如下
/* 存储每个线程要等待处理的客户端个数 */
redisAtomic unsigned long io_threads_pending[IO_THREADS_MAX_NUM];
io_threads_pending是个数组,每一个元素存储的是每个线程等待处理的客户端个数
5. 最后调用pthread_create创建线程,并传入了线程的运行函数IOThreadMain,之后将线程保存在io_threads中,io_threads数组存储了创建的线程描述符
总结一下main()到创建各个IO线程的流程,如下图
IO线程的运行函数 IOThreadMain
IO线程函数IOThreadMain()在networking.c文件中,实现如下(已加注释)
void *IOThreadMain(void *myid) {
/* myid是线程下标,从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;
}
/* 给主线程一个机会,可停止本IO线程 */
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);
}
}
解释下,函数的入参id表线程下标,然后函数开启了一个while(1)循环,主要处理逻辑如下:
1. 从io_threads_list数组中获取当前线程要处理的客户端列表,放入到列表迭代器li中
2. 往后遍历迭代器,获取每一个待处理客户端client,判断io_threads_op的值
如果是写标记,调用调用writeToClient处理
如果是读标记,调用readQueryFromClient处理
可以看出来,io_threads_list数组中存储了每一个线程要处理的客户端列表,在线程运行函数IOThreadMain中会获取本IO线程待处理的客户端列表,遍历每一个客户端,然后根据读写类型调用不同的方法进行处理
接下来就去看两个函数writeToClient() 和 readQueryFromClient() 都干了些什么,以及Redis在何时将待处理的客户端加入到io_threads_list列表中的