在redis6之后,引入了多线程,主要是因为硬件的发展,IO设备的吞吐能力在大大增强,很适合同时多任务大批量数据读写。
在了解redis的多线程之前,先来大概看下C语言多线程与metux锁的使用:
void test_thread_mutex();
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int main(int argc, char **argv) {
test_thread_mutex();
}
void* thread_function(void* arg) {
pthread_mutex_lock(&mutex); // 获取互斥锁
sleep(10);
printf(" new thread! Thread ID: %lu\n", (unsigned long)pthread_self());
pthread_mutex_unlock(&mutex); // 释放互斥锁
return NULL;
}
void test_thread_mutex(){
pthread_t tid;
pthread_create(&tid, NULL, thread_function, NULL);//创建新线程并在新线程运行function
printf("Main thread start...\n");
pthread_mutex_lock(&mutex); // 获取互斥锁
printf("Main thread processing\n");
pthread_mutex_unlock(&mutex); // 释放互斥锁
pthread_join(tid, NULL);
}
代码很简单,如果没了解过,直接看redis多线程的源码有点阻碍。
启动多线程可以根据主机核数配置线程数:
官方的说明,对于四核主机,可以配置2-3个线程,对于八核主机可以配置6个线程。具体咱也没测过。最多可以配置#define IO_THREADS_MAX_NUM 128
个线程。
redis默认是不对读任务进行多任务处理的,如果需要启动读任务多线程处理需要在配置文件配置:
io-threads-do-reads yes
在main()方法中,进入InitServerLast(),完成bioInit()和initThreadIO():
初始化线程池完成后,缓存多个Io thread,thread2-4被bio线程占用,看名字可能是跟文件关闭、aof持久化等相关的。
查看其中一个子线程,可以看到该线程被阻塞:
由多线程初始化可知,最开始,由于主线程抢占了各个子线程的锁,每个子线程都在阻塞状态,因此主线程需要在某处释放锁,启动各个子线程:
通过pthread_mutex_unlock(&io_threads_mutex[id])
在IDE里ctrl+G,全局查找释放锁的位置:一个是在子线程本身,一个是在startThreadIO();
继续跟踪发现,只有handleClientsWithPendingWritesUsingThreads()中调用了startThreadedIO(),handleClientsWithPendingWritesUsingThreads主要的作用是处理pending的写任务,这里主要先看何时启动子线程:
在aeMain()中不断循环遍历获取epoll事件过程中:
在redis源码之:事件驱动epoll中我们分析过,eventloop不断循环遍历获取事件,然后读事件会通过handler调用到readQueryFromClient(),之前在redis源码之:客户端命令执行Command是以单线程的视角解读,现在来看看多线程下的逻辑:
那在多线程调用readQueryFromClient()的时候,何时结束子线程的调用,在主线程继续命令执行?在readQueryFromClient()执行到processInputBuffer?:
因此可以得出结论,在redis中,可以多线程执行多个client读数据,但是最后还是按client到达的先后顺序执行每个client对应的命令。事务的一致性跟单线程一样的。
对于写数据的场景,主要看handleClientsWithPendingWritesUsingThreads()
,套路跟读数据的情况差不多,分多个线程写数据到客户端,不过写数据后续没有命令执行的步骤。这里就不分析了。
停止多线程的地方主要有两处:
handleClientsWithPendingWritesUsingThreads中停止多线程、通过定时任务在serverCron中尝试关停多线程
在initServer()的时候,设置了timeEvent,并设置回调函数serverCron().,不断检查当前pending的写任务个数,判断是否暂停多线程。