Netty源码分析第2章(NioEventLoop)---->第8节: 执行任务队列
Netty源码分析第二章: NioEventLoop
第八节: 执行任务队列
继续回到NioEventLoop的run()方法:
protected void run() {
for (;;) {
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.SELECT:
//轮询io事件(1)
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
default:
}
cancelledKeys = 0;
needsToSelectAgain = false;
//默认是50
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
processSelectedKeys();
} finally {
runAllTasks();
}
} else {
//记录下开始时间
final long ioStartTime = System.nanoTime();
try {
//处理轮询到的key(2)
processSelectedKeys();
} finally {
//计算耗时
final long ioTime = System.nanoTime() - ioStartTime;
//执行task(3)
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
//代码省略
}
}
我们看到处理完轮询到的key之后, 首先记录下耗时, 然后通过runAllTasks(ioTime * (100 - ioRatio) / ioRatio)执行taskQueue中的任务
我们知道ioRatio默认是50, 所以执行完ioTime * (100 - ioRatio) / ioRatio后, 方法传入的值为ioTime, 也就是processSelectedKeys()的执行时间:
跟进runAllTasks方法:
protected boolean runAllTasks(long timeoutNanos) {
//定时任务队列中聚合任务
fetchFromScheduledTaskQueue();
//从普通taskQ里面拿一个任务
Runnable task = pollTask();
//task为空, 则直接返回
if (task == null) {
//跑完所有的任务执行收尾的操作
afterRunningAllTasks();
return false;
}
//如果队列不为空
//首先算一个截止时间(+50毫秒, 因为执行任务, 不要超过这个时间)
final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;
long runTasks = 0;
long lastExecutionTime;
//执行每一个任务
for (;;) {
safeExecute(task);
//标记当前跑完的任务
runTasks ++;
//当跑完64个任务的时候, 会计算一下当前时间
if ((runTasks & 0x3F) == 0) {
//定时任务初始化到当前的时间
lastExecutionTime = ScheduledFutureTask.nanoTime();
//如果超过截止时间则不执行(nanoTime()是耗时的)
if (lastExecutionTime >= deadline) {
break;
}
}
//如果没有超过这个时间, 则继续从普通任务队列拿任务
task = pollTask();
//直到没有任务执行
if (task == null) {
//记录下最后执行时间
lastExecutionTime = ScheduledFutureTask.nanoTime();
break;
}
}
//收尾工作
afterRunningAllTasks();
this.lastExecutionTime = lastExecutionTime;
return true;
}
首先会执行fetchFromScheduledTaskQueue()这个方法, 这个方法的意思是从定时任务队列中聚合任务, 也就是将定时任务中找到可以执行的任务添加到taskQueue中
我们跟进fetchFromScheduledTaskQueue()方法:
private boolean fetchFromScheduledTaskQueue() {
long nanoTime = AbstractScheduledEventExecutor.nanoTime();
//从定时任务队列中抓取第一个定时任务
//寻找截止时间为nanoTime的任务
Runnable scheduledTask = pollScheduledTask(nanoTime);
//如果该定时任务队列不为空, 则塞到普通任务队列里面
while (scheduledTask != null) {
//如果添加到普通任务队列过程中失败
if (!taskQueue.offer(scheduledTask)) {
//则重新添加到定时任务队列中
scheduledTaskQueue().add((ScheduledFutureTask<?>) scheduledTask);
return false;
}
//继续从定时任务队列中拉取任务
//方法执行完成之后, 所有符合运行条件的定时任务队列, 都添加到了普通任务队列中
scheduledTask = pollScheduledTask(nanoTime);
}
return true;
}
long nanoTime = AbstractScheduledEventExecutor.nanoTime() 代表从定时任务初始化到现在过去了多长时间
Runnable scheduledTask= pollScheduledTask(nanoTime) 代表从定时任务队列中拿到小于nanoTime时间的任务, 因为小于初始化到现在的时间, 说明该任务需要执行了
跟到其父类AbstractScheduledEventExecutor的pollScheduledTask(nanoTime)方法中:
protected final Runnable pollScheduledTask(long nanoTime) {
assert inEventLoop();
//拿到定时任务队列
Queue<ScheduledFutureTask<?>> scheduledTaskQueue = this.scheduledTaskQueue;
//peek()方法拿到第一个任务
ScheduledFutureTask<?> scheduledTask = scheduledTaskQueue == null ? null : scheduledTaskQueue.peek();
if (scheduledTask == null) {
return null;
}
if (scheduledTask.deadlineNanos() <= nanoTime) {
//从队列中删除
scheduledTaskQueue.remove();
//返回该任务
return scheduledTask;
}
return null;
}
我们看到首先获得当前类绑定的定时任务队列的成员变量
如果不为空, 则通过scheduledTaskQueue.peek()弹出第一个任务
如果当前任务小于传来的时间, 说明该任务需要执行, 则从定时任务队列中删除
我们继续回到fetchFromScheduledTaskQueue()方法中:
private boolean fetchFromScheduledTaskQueue() {
long nanoTime = AbstractScheduledEventExecutor.nanoTime();
//从定时任务队列中抓取第一个定时任务
//寻找截止时间为nanoTime的任务
Runnable scheduledTask = pollScheduledTask(nanoTime);
//如果该定时任务队列不为空, 则塞到普通任务队列里面
while (scheduledTask != null) {
//如果添加到普通任务队列过程中失败
if (!taskQueue.offer(scheduledTask)) {
//则重新添加到定时任务队列中
scheduledTaskQueue().add((ScheduledFutureTask<?>) scheduledTask);
return false;
}
//继续从定时任务队列中拉取任务
//方法执行完成之后, 所有符合运行条件的定时任务队列, 都添加到了普通任务队列中
scheduledTask = pollScheduledTask(nanoTime);
}
return true;
}
弹出需要执行的定时任务之后, 我们通过taskQueue.offer(scheduledTask)添加到taskQueue中, 如果添加失败, 则通过scheduledTaskQueue().add((ScheduledFutureTask<?>) scheduledTask)重新添加到定时任务队列中
如果添加成功, 则通过pollScheduledTask(nanoTime)方法继续添加, 直到没有需要执行的任务
这样就将定时任务队列需要执行的任务添加到了taskQueue中
回到runAllTasks(long timeoutNanos)方法中:
protected boolean runAllTasks(long timeoutNanos) {
//定时任务队列中聚合任务
fetchFromScheduledTaskQueue();
//从普通taskQ里面拿一个任务
Runnable task = pollTask();
//task为空, 则直接返回
if (task == null) {
//跑完所有的任务执行收尾的操作
afterRunningAllTasks();
return false;
}
//如果队列不为空
//首先算一个截止时间(+50毫秒, 因为执行任务, 不要超过这个时间)
final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;
long runTasks = 0;
long lastExecutionTime;
//执行每一个任务
for (;;) {
safeExecute(task);
//标记当前跑完的任务
runTasks ++;
//当跑完64个任务的时候, 会计算一下当前时间
if ((runTasks & 0x3F) == 0) {
//定时任务初始化到当前的时间
lastExecutionTime = ScheduledFutureTask.nanoTime();
//如果超过截止时间则不执行(nanoTime()是耗时的)
if (lastExecutionTime >= deadline) {
break;
}
}
//如果没有超过这个时间, 则继续从普通任务队列拿任务
task = pollTask();
//直到没有任务执行
if (task == null) {
//记录下最后执行时间
lastExecutionTime = ScheduledFutureTask.nanoTime();
break;
}
}
//收尾工作
afterRunningAllTasks();
this.lastExecutionTime = lastExecutionTime;
return true;
}
首先通过 Runnable task = pollTask() 从taskQueue中拿一个任务
任务不为空, 则通过 final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos 计算一个截止时间, 任务的执行时间不能超过这个时间
然后在for循环中通过safeExecute(task)执行task
我们跟到safeExecute(task)中:
protected static void safeExecute(Runnable task) {
try {
//直接调用run()方法执行
task.run();
} catch (Throwable t) {
//发生异常不终止
logger.warn("A task raised an exception. Task: {}", task, t);
}
}
这里直接调用task的run()方法进行执行, 其中发生异常, 只打印一条日志, 代表发生异常不终止, 继续往下执行
回到runAllTasks(long timeoutNanos)方法:
protected boolean runAllTasks(long timeoutNanos) {
//定时任务队列中聚合任务
fetchFromScheduledTaskQueue();
//从普通taskQ里面拿一个任务
Runnable task = pollTask();
//task为空, 则直接返回
if (task == null) {
//跑完所有的任务执行收尾的操作
afterRunningAllTasks();
return false;
}
//如果队列不为空
//首先算一个截止时间(+50毫秒, 因为执行任务, 不要超过这个时间)
final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;
long runTasks = 0;
long lastExecutionTime;
//执行每一个任务
for (;;) {
safeExecute(task);
//标记当前跑完的任务
runTasks ++;
//当跑完64个任务的时候, 会计算一下当前时间
if ((runTasks & 0x3F) == 0) {
//定时任务初始化到当前的时间
lastExecutionTime = ScheduledFutureTask.nanoTime();
//如果超过截止时间则不执行(nanoTime()是耗时的)
if (lastExecutionTime >= deadline) {
break;
}
}
//如果没有超过这个时间, 则继续从普通任务队列拿任务
task = pollTask();
//直到没有任务执行
if (task == null) {
//记录下最后执行时间
lastExecutionTime = ScheduledFutureTask.nanoTime();
break;
}
}
//收尾工作
afterRunningAllTasks();
this.lastExecutionTime = lastExecutionTime;
return true;
}
每次执行完task, runTasks自增
这里 if ((runTasks & 0x3F) == 0) 代表是否执行了64个任务, 如果执行了64个任务, 则会通过 lastExecutionTime = ScheduledFutureTask.nanoTime() 记录定时任务初始化到现在的时间, 如果这个时间超过了截止时间, 则退出循环
如果没有超过截止时间, 则通过 task = pollTask() 继续弹出任务执行
这里执行64个任务统计一次时间, 而不是每次执行任务都统计, 主要原因是因为获取系统时间是个比较耗时的操作, 这里是netty的一种优化方式
如果没有task需要执行, 则通过afterRunningAllTasks()做收尾工作, 最后记录下最后的执行时间
以上就是有关执行任务队列的相关逻辑
第二章总结
本章学习了有关NioEventLoopGroup的创建, NioEventLoop的创建和启动, 以及多路复用器的轮询处理和task执行的相关逻辑, 通过本章学习, 我们应该掌握如下内容:
1. NioEventLoopGroup如何选择分配NioEventLoop
2. NioEventLoop如何开启
3. NioEventLoop如何进行select操作
4. NioEventLoop如何执行task
下一节: 初始化NioSocketChannelConfig
Netty源码分析第2章(NioEventLoop)---->第8节: 执行任务队列的更多相关文章
- Netty源码分析第2章(NioEventLoop)---->第6节: 执行select操作
Netty源码分析第二章: NioEventLoop 第六节: 执行select操作 分析完了selector的创建和优化的过程, 这一小节分析select相关操作 跟到跟到select操作的入口 ...
- Netty源码分析第2章(NioEventLoop)---->第1节: NioEventLoopGroup之创建线程执行器
Netty源码分析第二章: NioEventLoop 概述: 通过上一章的学习, 我们了解了Server启动的大致流程, 有很多组件与模块并没有细讲, 从这个章开始, 我们开始详细剖析netty的各个 ...
- Netty源码分析第2章(NioEventLoop)---->第2节: NioEventLoopGroup之NioEventLoop的创建
Netty源码分析第二章: NioEventLoop 第二节: NioEventLoopGroup之NioEventLoop的创建 回到上一小节的MultithreadEventExecutorG ...
- Netty源码分析第2章(NioEventLoop)---->第3节: 初始化线程选择器
Netty源码分析第二章:NioEventLoop 第三节:初始化线程选择器 回到上一小节的MultithreadEventExecutorGroup类的构造方法: protected Multi ...
- Netty源码分析第2章(NioEventLoop)---->第4节: NioEventLoop线程的启动
Netty源码分析第二章: NioEventLoop 第四节: NioEventLoop线程的启动 之前的小节我们学习了NioEventLoop的创建以及线程分配器的初始化, 那么NioEvent ...
- Netty源码分析第2章(NioEventLoop)---->第5节: 优化selector
Netty源码分析第二章: NioEventLoop 第五节: 优化selector 在剖析selector轮询之前, 我们先讲解一下selector的创建过程 回顾之前的小节, 在创建NioEv ...
- Netty源码分析第2章(NioEventLoop)---->第7节: 处理IO事件
Netty源码分析第二章: NioEventLoop 第七节:处理IO事件 上一小节我们了解了执行select()操作的相关逻辑, 这一小节我们继续学习select()之后, 轮询到io事件的相关 ...
- Netty源码分析第4章(pipeline)---->第7节: 前章节内容回顾
Netty源码分析第四章: pipeline 第七节: 前章节内容回顾 我们在第一章和第三章中, 遗留了很多有关事件传输的相关逻辑, 这里带大家一一回顾 首先看两个问题: 1.在客户端接入的时候, N ...
- Netty源码分析第5章(ByteBuf)---->第10节: SocketChannel读取数据过程
Netty源码分析第五章: ByteBuf 第十节: SocketChannel读取数据过程 我们第三章分析过客户端接入的流程, 这一小节带大家剖析客户端发送数据, Server读取数据的流程: 首先 ...
随机推荐
- const修饰的成员是类成员,还是实例成员?
很抱歉,我以为只有static修饰的成员是类成员,可以通过类名直接访问,然而,const 修饰的成员也属于类成员,直接通过类名访问,不能通过实例变量访问. 做维护久了,深刻的理解,扎实的基础对写出高质 ...
- Mysql索引详解及优化(key和index区别)
MySQL索引的概念 索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针.更通俗的说,数据库索引好比是一本书前面的目录,能加快数据库 ...
- 深入理解C/C++混合编程优秀博文赏析与学习
1.深入理解C/C++混合编程 2.C/C++ 混合编程 互相调用 3.C代码中如何调用C++ C++中如何调用C 4.C和C++混合编程
- Spring源码分析(七)bean标签的解析及注册
摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 在上一篇中提到过Spring中的标签包括默认标签和自定义标签两种,而两种 ...
- P1470 最长前缀 Longest Prefix
题目描述 在生物学中,一些生物的结构是用包含其要素的大写字母序列来表示的.生物学家对于把长的序列分解成较短的序列(即元素)很感兴趣. 如果一个集合 P 中的元素可以通过串联(元素可以重复使用,相当于 ...
- HDU 1950(LIS)
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1950 Bridging signals Time Limit: 5000/1000 MS (Java ...
- 【OC底层】AssociatedObject 关联对象
如何实现给分类“添加成员变量”? 默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中.但可以通过关联对象来间接实现 关联对象提供了以下API 1> 添加关联对象 void objc_s ...
- Linux下onvif客户端获取h265 IPC摄像头的RTSP地址
1. 设备搜索,去获取webserver 的地址 ,目的是在获取能力提供服务地址,demo:https://www.cnblogs.com/croxd/p/10683429.html 2. GetCa ...
- 尝试用docker上的jenkins
比起dockerfile的编写,我更习惯使用简单的docker-compose.yml.但是官方的docker-compose.yml并不是独立的.于是经过一番研究,暂时打造了自己的文件: docke ...
- GATK--数据预处理,质控,检测变异
版权声明:本文源自 解螺旋的矿工, 由 XP 整理发表,共 13781 字. 转载请注明:从零开始完整学习全基因组测序(WGS)数据分析:第4节 构建WGS主流程 | Public Library o ...