多线程模式中,在main函数中会执行InitServerLast

void InitServerLast() {
bioInit();
// 关键一步, 这里启动了多条线程,用于执行命令,redis起名为IO 线程
initThreadedIO();
set_jemalloc_bg_thread(server.jemalloc_bg_thread);
server.initial_memory_usage = zmalloc_used_memory();
}

1. 看initThreadedIO

networking.c

void initThreadedIO(void) {
server.io_threads_active = 0; /* We start with threads not active. */ /* Indicate that io-threads are currently idle */
io_threads_op = IO_THREADS_OP_IDLE; /* Don't spawn any thread if the user selected a single thread:
* we'll handle I/O directly from the main thread. */
if (server.io_threads_num == 1) return; 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);
} /* Spawn and initialize the I/O threads. */
for (int i = 0; i < server.io_threads_num; i++) {
/* Things we do for all the threads including the main thread. */
// 为线程创建一个 list,并将该list 的指针加入到io_threads_list中
io_threads_list[i] = listCreate();
if (i == 0) continue; /* Thread 0 is the main thread. */ /* Things we do only for the additional threads. */
pthread_t tid;
pthread_mutex_init(&io_threads_mutex[i],NULL);
setIOPendingCount(i, 0);
pthread_mutex_lock(&io_threads_mutex[i]); /* Thread will be stopped. */
// 这里创建线程,并将线程id 假如到io_threads数组中
if (pthread_create(&tid,NULL,IOThreadMain,(void*)(long)i) != 0) {
serverLog(LL_WARNING,"Fatal: Can't initialize IO thread.");
exit(1);
}
io_threads[i] = tid;
}
}
这样在该函数中,创建了线程 并 为该线程分配了一个 list

2. 当client 发命令过来时,会调用到readQueryFromClient

void readQueryFromClient(connection *conn) {
client *c = connGetPrivateData(conn);
int nread, big_arg = 0;
size_t qblen, readlen; /* Check if we want to read from the client later when exiting from
* the event loop. This is the case if threaded I/O is enabled. */
// 多线程模式时,在这里会return
if (postponeClientRead(c)) return;
......
}

3. 看postponeClientRead

int postponeClientRead(client *c) {
if (server.io_threads_active &&
server.io_threads_do_reads &&
!ProcessingEventsWhileBlocked &&
!(c->flags & (CLIENT_MASTER|CLIENT_SLAVE|CLIENT_BLOCKED)) &&
io_threads_op == IO_THREADS_OP_IDLE)
{
// 把该client 假如到server的clients_pending_read中,放到list头
listAddNodeHead(server.clients_pending_read中,c);
// 互相引用一下
c->pending_read_list_node = listFirst(server.clients_pending_read);
return 1;
} else {
return 0;
}
}

4. IO线程创建后 持续干活,会调用IOThreadMain

void *IOThreadMain(void *myid) {
/* The ID is the thread number (from 0 to server.iothreads_num-1), and is
* used by the thread to just manipulate a single sub-array of clients. */
// myid 也是对应的list数组的id
long id = (unsigned long)myid;
char thdname[16]; ...... while(1) {
...... /* Process: note that the main thread will never touch our list
* before we drop the pending count to 0. */
listIter li;
listNode *ln;
// 拿到对应的 list
listRewind(io_threads_list[id],&li);
// 遍历list
while((ln = listNext(&li))) {
client *c = listNodeValue(ln);
if (io_threads_op == IO_THREADS_OP_WRITE) {
writeToClient(c,0);
} else if (io_threads_op == IO_THREADS_OP_READ) {
// 执行读取操作
readQueryFromClient(c->conn);
} else {
serverPanic("io_threads_op value is unknown");
}
}
listEmpty(io_threads_list[id]);
setIOPendingCount(id, 0);
}
}
那么什么时候去将io_threads_op 设置为IO_THREADS_OP_READ
在eventloop 的 beforeSleep中, beforeSleep 会调用handleClientsWithPendingReadsUsingThreads
int handleClientsWithPendingReadsUsingThreads(void) {
......
/* Distribute the clients across N different lists. */
listIter li;
listNode *ln;
// 获取之前postponeClientRead中添加到list中 的所有元素list
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;
// 将元素即对应的client,添加到对应的线程的list中去
listAddNodeTail(io_threads_list[target_id],c);
item_id++;
} /* Give the start condition to the waiting threads, by setting the
* start condition atomic var. */
// 将io线程标志 置为 IO_THREADS_OP_READ
io_threads_op = IO_THREADS_OP_READ;
// 设置所有 IO线程 待处理的请求的个数,求和
for (int j = 1; j < server.io_threads_num; j++) {
int count = listLength(io_threads_list[j]);
setIOPendingCount(j, count);
} /* Also use the main thread to process a slice of clients. */
listRewind(io_threads_list[0],&li);
// 主线程也有自己的 list,即index == 0 的位置的list, 在这里也参与IO操作,不浪费一点性能
while((ln = listNext(&li))) {
client *c = listNodeValue(ln);
readQueryFromClient(c->conn);
}
listEmpty(io_threads_list[0]); /* Wait for all the other threads to end their work. */
// 这里是再次求和,知道发现所有的客户端请求完成才退出循环
while(1) {
unsigned long pending = 0;
for (int j = 1; j < server.io_threads_num; j++)
pending += getIOPendingCount(j);
if (pending == 0) break;
} // 这里都执行完成了,再讲IO线程标志位职位IDLE
io_threads_op = IO_THREADS_OP_IDLE;
......
}
从源码中的解释也可以看出,虽然加入了多线程,加快了命令处理的速度,但是如果某个命令特别耗时,
会影响 beforeSleep函数一直卡住,导致后续的命令一直pending,不能加入到io线程的list中

所以还是要注意命令的执行效率

5. 最后每个线程单独调用 readQueryFromClient, 由上面分析,当前io_threads_op 为 read 状态
void readQueryFromClient(connection *conn) {
client *c = connGetPrivateData(conn);
int nread, big_arg = 0;
size_t qblen, readlen; /* Check if we want to read from the client later when exiting from
* the event loop. This is the case if threaded I/O is enabled. */
// 此时不会return, 会往下执行命令操作了
if (postponeClientRead(c)) return;
......
} int postponeClientRead(client *c) {
// io线程来的, io_threads_op 为 read,返回0
if (server.io_threads_active &&
server.io_threads_do_reads &&
!ProcessingEventsWhileBlocked &&
!(c->flags & (CLIENT_MASTER|CLIENT_SLAVE|CLIENT_BLOCKED)) &&
io_threads_op == IO_THREADS_OP_IDLE)
{
listAddNodeHead(server.clients_pending_read,c);
c->pending_read_list_node = listFirst(server.clients_pending_read);
return 1;
} else {
return 0;
}
}

到此分析结束

多线程模型为 主线程接收 accept 和 read, write, 但不操作,将对应的client 假如到对应iothread的list中,然后由子线程处理。

redis7源码分析:redis 多线程模型解析的更多相关文章

  1. Memcached源码分析之线程模型

    作者:Calix 一)模型分析 memcached到底是如何处理我们的网络连接的? memcached通过epoll(使用libevent,下面具体再讲)实现异步的服务器,但仍然使用多线程,主要有两种 ...

  2. Spring源码分析之AOP从解析到调用

    正文: 在上一篇,我们对IOC核心部分流程已经分析完毕,相信小伙伴们有所收获,从这一篇开始,我们将会踏上新的旅程,即Spring的另一核心:AOP! 首先,为了让大家能更有效的理解AOP,先带大家过一 ...

  3. springMVC源码分析--HandlerMethodReturnValueHandlerComposite返回值解析器集合(二)

    在上一篇博客springMVC源码分析--HandlerMethodReturnValueHandler返回值解析器(一)我们介绍了返回值解析器HandlerMethodReturnValueHand ...

  4. spring源码分析系列 (15) 设计模式解析

    spring是目前使用最为广泛的Java框架之一.虽然spring最为核心是IOC和AOP,其中代码实现中很多设计模式得以应用,代码看起来简洁流畅,在日常的软件设计中很值得借鉴.以下是对一些设计模式的 ...

  5. 【Redis】事件驱动框架源码分析(多线程)

    IO线程初始化 Redis在6.0版本中引入了多线程,提高IO请求处理效率. 在Redis Server启动函数main(server.c文件)中初始化服务之后,又调用了InitServerLast函 ...

  6. jQuery 2.0.3 源码分析Sizzle引擎 - 词法解析

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 浏览器从下载文档到显示页面的过程是个复杂的过程,这里包含了重绘和重排.各家浏览器引擎的工作原理略有差别,但也有一定规则. 简 ...

  7. Spring源码分析(九)解析默认标签中的自定义标签元素

    摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 到这里我们已经完成了分析默认标签的解析与提取过程,或许涉及的内容太多,我 ...

  8. Spring源码分析(六)解析和注册BeanDefinitions

    摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 当把文件转换为Document后,接下来的提取及注册bean就是我们的重 ...

  9. mybatis源码分析之02配置文件解析

    该篇正式开始学习mybatis的源码,本篇主要学习mybatis是如何加载配置文件mybatis-config.xml的, 先从测试代码入手. public class V1Test { public ...

  10. trinitycore 魔兽服务器源码分析(三) 多线程相关

    先看LockedQueue.h template <class T, typename StorageType = std::deque<T> >class LockedQue ...

随机推荐

  1. [转帖]elasticsearch8.0以上版本修改内置用户密码

    https://www.cnblogs.com/zhang-ding-1314/p/16199682.html 修改密码需要在es启动,并cd到es的bin目录下执行: 1.重置密码并在控制台显示新密 ...

  2. ESXi虚拟化的坑-细微区别下虚拟机性能差异巨大

    ESXi虚拟化的坑-细微区别下虚拟机性能差异巨大 背景 周末在公司无偿加班. 同组的小伙伴周一有一个需求, 我想着周六乘着机器压力不大进行一下虚拟机的clone 但是截止到晚上快十点都没有完全Clon ...

  3. 【转帖】JVM的发展历程

    目录 1.Sun Classic VM 2.Exact VM 3.Sun HotSpot(主流) 4.JRockit 5.IBM J9 6.下一代虚拟机Graal VM 1.Sun Classic V ...

  4. [转帖]Linux系统:page fault

    Linux进程如何访问内存 Linux下,进程并不是直接访问物理内存,而是通过内存管理单元(MMU)来访问内存资源,原因后面会讲到. 为什么需要虚拟内存地址空间 假设某个进程需要4MB的空间,内存假设 ...

  5. [转帖]陈巍谈芯:NLP里比BERT更优秀的XLNet长什么样?

    https://zhuanlan.zhihu.com/p/447836322 ​ 目录 收起 一.XLNet的优势 1)独得AR与AE两大绝学 2)集成了Tansformer-XL 二.XLNet的结 ...

  6. awk的简要使用

    原文地址:https://www.lujun9972.win/blog/2020/08/23/在命令行进行简单的统计分析/index.html 目录 使用awk获取最小值.最大值.中位数和平均值 使用 ...

  7. 神通奥斯卡数据库是否兼容Oracle, 以及参数修改的办法

    1. 最近公司要适配神通数据库, 但是因为一些功能异常.参数可能存在风险. 为了减少问题, 想着简单描述一下这些的处理. 开发和客户给的默认参数建议 1. 不选择 兼容oracle模式 2. 字符集选 ...

  8. rfc7230 Message Syntax and Routing

    rfc7230 目录 rfc7230 2 Architecture 2.6 Protocol Versioning 3 Message Format 3.1 Start Line 3.1.1 Requ ...

  9. 开源项目01--WTM

    一.项目名称:WTM 项目所用技术栈: wtm mvvm mvc aspnetcore dotnetcore react vue layui layui-admin element-ui ncc等 项 ...

  10. 关于git的几点疑问

    git rename后查看之前的记录 对于某个文件进行rename之后,使用show log命令查看之前的修改记录都会丢失,通过命令行方式进行mv之后,在tortoisegit中查看记录还是丢失的 g ...