Redis多线程IO源码分析-第一篇

由于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列表中的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值