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

上一节: 处理IO事件

下一节: 初始化NioSocketChannelConfig

Netty源码分析第2章(NioEventLoop)---->第8节: 执行任务队列的更多相关文章

  1. Netty源码分析第2章(NioEventLoop)---->第6节: 执行select操作

    Netty源码分析第二章: NioEventLoop   第六节: 执行select操作 分析完了selector的创建和优化的过程, 这一小节分析select相关操作 跟到跟到select操作的入口 ...

  2. Netty源码分析第2章(NioEventLoop)---->第1节: NioEventLoopGroup之创建线程执行器

    Netty源码分析第二章: NioEventLoop 概述: 通过上一章的学习, 我们了解了Server启动的大致流程, 有很多组件与模块并没有细讲, 从这个章开始, 我们开始详细剖析netty的各个 ...

  3. Netty源码分析第2章(NioEventLoop)---->第2节: NioEventLoopGroup之NioEventLoop的创建

    Netty源码分析第二章: NioEventLoop   第二节: NioEventLoopGroup之NioEventLoop的创建 回到上一小节的MultithreadEventExecutorG ...

  4. Netty源码分析第2章(NioEventLoop)---->第3节: 初始化线程选择器

    Netty源码分析第二章:NioEventLoop   第三节:初始化线程选择器 回到上一小节的MultithreadEventExecutorGroup类的构造方法: protected Multi ...

  5. Netty源码分析第2章(NioEventLoop)---->第4节: NioEventLoop线程的启动

    Netty源码分析第二章: NioEventLoop   第四节: NioEventLoop线程的启动 之前的小节我们学习了NioEventLoop的创建以及线程分配器的初始化, 那么NioEvent ...

  6. Netty源码分析第2章(NioEventLoop)---->第5节: 优化selector

    Netty源码分析第二章: NioEventLoop   第五节: 优化selector 在剖析selector轮询之前, 我们先讲解一下selector的创建过程 回顾之前的小节, 在创建NioEv ...

  7. Netty源码分析第2章(NioEventLoop)---->第7节: 处理IO事件

    Netty源码分析第二章: NioEventLoop   第七节:处理IO事件 上一小节我们了解了执行select()操作的相关逻辑, 这一小节我们继续学习select()之后, 轮询到io事件的相关 ...

  8. Netty源码分析第4章(pipeline)---->第7节: 前章节内容回顾

    Netty源码分析第四章: pipeline 第七节: 前章节内容回顾 我们在第一章和第三章中, 遗留了很多有关事件传输的相关逻辑, 这里带大家一一回顾 首先看两个问题: 1.在客户端接入的时候, N ...

  9. Netty源码分析第5章(ByteBuf)---->第10节: SocketChannel读取数据过程

    Netty源码分析第五章: ByteBuf 第十节: SocketChannel读取数据过程 我们第三章分析过客户端接入的流程, 这一小节带大家剖析客户端发送数据, Server读取数据的流程: 首先 ...

随机推荐

  1. AOP的本质

    AOP的本质是HOOK: HOOK的本质是:新函数包含原函数或新函数替换原函数: 需要解决的问题: 1.新函数的生成: 2.新函数的调用机制: 3.原函数的调用机制: 新函数的生成: 1.将已有的动态 ...

  2. 【洛谷】【lca+树上差分】P3258 [JLOI2014]松鼠的新家

    [题目描述:] 松鼠的新家是一棵树,前几天刚刚装修了新家,新家有n(2 ≤ n ≤ 300000)个房间,并且有n-1根树枝连接,每个房间都可以相互到达,且俩个房间之间的路线都是唯一的.天哪,他居然真 ...

  3. 【转】 Class.forName()用法及与new区别 详解

    平时开发中我们经常会发现:用到Class.forName()方法.为什么要用呢? 下面分析一下: 主要功能Class.forName(xxx.xx.xx)返回的是一个类Class.forName(xx ...

  4. 命令行编译执行java

    命令行编译运行java程序 使用命令 javac进行编译 和 java进行执行. javac 后面跟着的是java文件的文件名,例如 HelloWorld.java. 该命令用于将 java 源文件编 ...

  5. 愤怒的小鸟【$DP$优化】

    卡常的状压\(DP\),愤怒的小鸟. 其实本来是个很水的状压\(DP\),但因为最后三个点\(n=18\),成功地把我的不可能达到的下界为\(\Omega(2^nn^2)\),紧确的上界为\(O(2^ ...

  6. K2 4.7 升级 数据库排序规则更改

    介绍 在过去,K2没有指定安装过程中要在其数据库上使用的标准排序规则.然而,现在K2引入了标准排序规则,以便在之后使用(如果我没有错的话,它是在4.7). 因此, 问题出现在数据库的排序规则不是Lat ...

  7. 手动增加swap分区

    第一步:创建一个大小为6G的文件:#dd if=/dev/zero of=/swapfile bs=1G count=6第二步:把这个文件变成swap文件:#mkswap /swapfile第三步:启 ...

  8. 安装github教程

    1.注意事项 .在FF和chrome会把GitHub.application当成一个应用程序下载下来,安装下载下来的程序也会失败的,只能通过ie直接安装. 2.要用https,不能用http. 2.安 ...

  9. iOS开发网络篇—发送GET和POST请求(使用NSURLSession) - 转

    说明: 1.该文主要介绍如何使用NSURLSession来发送GET请求和POST请求 2.本文将不再讲解NSURLConnection的使用,如有需要了解NSURLConnection如何发送请求. ...

  10. Deepin15.8系统下安装QorIQ Linux SDK v2.0 yocto成功完美运行的随笔

    2019.2.17日:最终安装成功,完美解决! 2019.2.16日:最终安装未成功,但是过程中排除 了几个bug,前进了几步,仅供参考. 写在最前面,yocto安装是有系统要求的,Deepin 15 ...