回顾NioEventLoop的run方法流程

上文说到NioEventLoop的run方法可以分为3个步骤:

  1. 轮询channel中就绪的IO事件
  2. 处理轮询出的IO事件
  3. 处理所有任务,也包括定时任务

其中步骤1已在上一节讲述,这里接着讲述下面2个步骤

IO事件与非IO任务

首先看一下在步骤2和步骤3的主干代码

  1. final int ioRatio = this.ioRatio;
  2. // 将所有任务执行完
  3. if (ioRatio == 100) {
  4. try {
  5. processSelectedKeys();
  6. } finally {
  7. // Ensure we always run tasks.
  8. runAllTasks();
  9. }
  10. } else {
  11. // 记录IO事件消耗的时间,然后按比例处理分配时间处理非IO任务
  12. final long ioStartTime = System.nanoTime();
  13. try {
  14. processSelectedKeys();
  15. } finally {
  16. // Ensure we always run tasks.
  17. final long ioTime = System.nanoTime() - ioStartTime;
  18. // ioRatio默认50,(100-ioRatio)/ioRatio刚好等于1,做到平均分配
  19. runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
  20. }
  21. }

ioRadio是NioEventLoop的一个成员变量,用来控制分配花费在IO事件与非IO任务时间的比例。默认情况下,ioRadio是50,表示IO事件与非IO任务

将分配相同时间。而当ioRatio为100时,该值失效,不再平衡两种动作的时间分配比值。

了解了这一点,上述两种分支代码就不难理解了,我们直接进入processSelectedKeys,看看netty如何执行IO事件

处理IO事件

先进入processSelectedKeys方法内部。

  1. private void processSelectedKeys() {
  2. if (selectedKeys != null) {
  3. processSelectedKeysOptimized();
  4. } else {
  5. processSelectedKeysPlain(selector.selectedKeys());
  6. }
  7. }

可以看到这里又根据selectedKeys是否为空这个条件来确定是处理优化过的keys还是普通keys。关于selectedKeys,在NioEventLoop介绍这一节中,

我们介绍了NioEventLoop的创建,在创建过程中,默认会将SelectedKeys由Hashset替换为数组实现,此处的selectedKeys正是替换过后的实现。

我们继续跟进到processSelectedKeysOptimized方法

  1. private void processSelectedKeysOptimized() {
  2. for (int i = 0; i < selectedKeys.size; ++i) {
  3. final SelectionKey k = selectedKeys.keys[i];
  4. selectedKeys.keys[i] = null;
  5. final Object a = k.attachment();
  6. if (a instanceof AbstractNioChannel) {
  7. processSelectedKey(k, (AbstractNioChannel) a);
  8. } else {
  9. NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
  10. processSelectedKey(k, task);
  11. }
  12. if (needsToSelectAgain) {
  13. selectedKeys.reset(i + 1);
  14. selectAgain();
  15. i = -1;
  16. }
  17. }
  18. }

方法内部用一个for循环处理selectedKeys。key的attchment默认是在注册时附加上去的NioServerSocketChannel和NioSocketChannel。

继续跟进processSelectedKey(k, (AbstractNioChannel) a)方法。

  1. private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
  2. final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
  3. if (!k.isValid()) {
  4. final EventLoop eventLoop = ch.eventLoop();
  5. if (eventLoop != this || eventLoop == null) {
  6. return;
  7. }
  8. unsafe.close(unsafe.voidPromise());
  9. return;
  10. }
  11. int readyOps = k.readyOps();
  12. if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
  13. int ops = k.interestOps();
  14. ops &= ~SelectionKey.OP_CONNECT;
  15. k.interestOps(ops);
  16. unsafe.finishConnect();
  17. }
  18. if ((readyOps & SelectionKey.OP_WRITE) != 0) {
  19. ch.unsafe().forceFlush();
  20. }
  21. if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
  22. unsafe.read();
  23. }
  24. }

netty首先对selectionKey的有效性做了一个判断。当key无效时,关闭key所在的channel。当key有效时,委托NioUnsafe对象对key进行IO操作。

注意这里先进行OP_CONNECT,再执行OP_WRITE,最后执行OP_READ和OP_ACCEPT。关于Unsafe的这些IO操作留待以后分析。

processSelectedKeysPlain方法流程类似,略过

处理非IO任务

由于IoRatio默认为50,我们先进入runAllTasks(ioTime * (100 - ioRatio) / ioRatio)方法。

  1. protected boolean runAllTasks(long timeoutNanos) {
  2. // 步骤1
  3. fetchFromScheduledTaskQueue();
  4. // 步骤2
  5. Runnable task = pollTask();
  6. if (task == null) {
  7. afterRunningAllTasks();
  8. return false;
  9. }
  10. // 步骤3
  11. final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;
  12. long runTasks = 0;
  13. long lastExecutionTime;
  14. for (;;) {
  15. // 步骤4
  16. safeExecute(task);
  17. runTasks ++;
  18. // 步骤5
  19. if ((runTasks & 0x3F) == 0) {
  20. lastExecutionTime = ScheduledFutureTask.nanoTime();
  21. if (lastExecutionTime >= deadline) {
  22. break;
  23. }
  24. }
  25. task = pollTask();
  26. if (task == null) {
  27. lastExecutionTime = ScheduledFutureTask.nanoTime();
  28. break;
  29. }
  30. }
  31. // 步骤6
  32. afterRunningAllTasks();
  33. this.lastExecutionTime = lastExecutionTime;
  34. return true;
  35. }

非IO任务的执行可以分为6个步骤

  1. 从定时任务队列聚合任务到普通任务队列
  2. 从普通队列中获取任务
  3. 计算任务执行的超时时间
  4. 安全执行任务
  5. 任务执行到一定次数,计算是否超时
  6. 执行完taskQueue普通队列里的任务后,再去执行tailTaskQueue里的任务。但目前暂时没有看到tailTaskQueue使用的地方,也许是一个扩展点吧,这里先略过。

我们一个一个步骤讲解

聚合定时任务到普通任务队列

首先看一下整体流程

  1. private boolean fetchFromScheduledTaskQueue() {
  2. if (scheduledTaskQueue == null || scheduledTaskQueue.isEmpty()) {
  3. return true;
  4. }
  5. long nanoTime = AbstractScheduledEventExecutor.nanoTime();
  6. for (;;) {
  7. Runnable scheduledTask = pollScheduledTask(nanoTime);
  8. if (scheduledTask == null) {
  9. return true;
  10. }
  11. if (!taskQueue.offer(scheduledTask)) {
  12. scheduledTaskQueue.add((ScheduledFutureTask<?>) scheduledTask);
  13. return false;
  14. }
  15. }
  16. }

首先先判断定时任务队列是否有任务,然后调用了一个AbstractScheduledEventExecutor.nanoTime(),该方法返回ScheduledFutureTask类从初始化

到当前时刻的差值。也即将ScheduledFutureTask初始化的时刻当成零时刻。

获取到零时刻到当前时刻的差值后,用一个for循环不断去定时任务队列里获取终止时刻在当前时刻之后的任务(scheduledTask.deadlineNanos() - nanoTime<=0)

当获取到定时任务后,将它添加到普通任务队列taskQueue里。同时添加失败后,还会再重新添加回定时任务队列,防止任务直接丢失。

说到定时任务队列,也少不了一探其实现。scheduledTaskQueue初始化代码如下:

  1. PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue() {
  2. if (scheduledTaskQueue == null) {
  3. scheduledTaskQueue = new DefaultPriorityQueue<>(
  4. SCHEDULED_FUTURE_TASK_COMPARATOR,
  5. 11);
  6. }
  7. return scheduledTaskQueue;
  8. }

采用的是一个懒加载的方式,在调用scheduledTaskQueue()创建定时任务时才进行初始化。从名字可以看出,它是一个优先级队列,初始化容量为11,

采用的Comparator是调用2个ScheduledFutureTask的compareTo方法,首先比较任务的终止时间,然后比较两个任务的id。代码较简单,就不列了。

然后我们看下调度方法schedule

  1. private <V> ScheduledFuture<V> schedule(final ScheduledFutureTask<V> task) {
  2. if (inEventLoop()) {
  3. scheduledTaskQueue().add(task.setId(nextTaskId++));
  4. } else {
  5. executeScheduledRunnable(new Runnable() {
  6. @Override
  7. public void run() {
  8. scheduledTaskQueue().add(task.setId(nextTaskId++));
  9. }
  10. }, true, task.deadlineNanos());
  11. }
  12. return task;
  13. }

可以发现,netty将"添加定时任务"也当做一个任务,放入任务队列里。

从普通队列中获取任务

  1. // NioEventLoop中定义的pollTask方法
  2. protected Runnable pollTask() {
  3. Runnable task = super.pollTask();
  4. if (needsToSelectAgain) {
  5. selectAgain();
  6. }
  7. return task;
  8. }
  9. // super.pollTask调用了此方法,定义在SingleThreadEventExecutor中
  10. protected static Runnable pollTaskFrom(Queue<Runnable> taskQueue) {
  11. for (;;) {
  12. Runnable task = taskQueue.poll();
  13. if (task != WAKEUP_TASK) {
  14. return task;
  15. }
  16. }
  17. }

这里依然是通过轮询从任务队列里取出任务,并且忽略WAKEUP_TASK这个标记性任务。

计算任务执行的超时时间

在当前时间上,加上IO事件执行的时间,作为非IO任务执行的超时时间

安全执行

  1. protected static void safeExecute(Runnable task) {
  2. try {
  3. task.run();
  4. } catch (Throwable t) {
  5. logger.warn("A task raised an exception. Task: {}", task, t);
  6. }
  7. }

捕获所有异常,使得定时任务报错时不退出

计算是否超时

由于nanoTime()是一个相对耗时的操作,netty默认执行了64次非IO任务后,才计算是否超时。若执行了超过64个任务没或者任务队列已经没有任务,

就打断循环,并将当前时间更新为lastExecutionTime。

总结

到了这里,我们已经介绍完了大部分NioEventLoop的内容,限于笔者水平和文章篇幅,nioEventLoop所使用的任务队列MpscQueue和ScheduleFutureTask

内部执行原理不再进一步深究。但这也已经足够对NioEventLoop塑造一个比较整体性的认识了。

4.Netty执行IO事件和非IO任务的更多相关文章

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

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

  2. 一文聊透 Netty IO 事件的编排利器 pipeline | 详解所有 IO 事件的触发时机以及传播路径

    欢迎关注公众号:bin的技术小屋,本文图片加载不出来的话可查看公众号原文 本系列Netty源码解析文章基于 4.1.56.Final版本 1. 前文回顾 在前边的系列文章中,笔者为大家详细剖析了 Re ...

  3. 从操作系统内核看Java非阻塞IO事件检测

    非阻塞服务器模型最重要的一个特点是,在调用读取或写入接口后立即返回,而不会进入阻塞状态.在探讨单线程非阻塞IO模型前必须要先了解非阻塞情况下Socket事件的检测机制,因为对于非阻塞模式最重要的事情是 ...

  4. Netty Reator(二)Scalable IO in Java

    Netty Reator(二)Scalable IO in Java Netty 系列目录 (https://www.cnblogs.com/binarylei/p/10117436.html) Do ...

  5. 网络IO模型:同步IO和异步IO,阻塞IO和非阻塞IO

    同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别?这个问题其实不同的人给出 ...

  6. 转 网络IO模型:同步IO和异步IO,阻塞IO和非阻塞IO

    此文章为转载,如有侵权,请联系本人.转载出处,http://blog.chinaunix.net/uid-28458801-id-4464639.html 同步(synchronous) IO和异步( ...

  7. Python网络编程-IO阻塞与非阻塞及多路复用

    前言 问题:普通套接字实现的服务端的缺陷 一次只能服务一个客户端!                         accept阻塞! 在没有新的套接字来之前,不能处理已经建立连接的套接字的请求 re ...

  8. python 全栈开发,Day44(IO模型介绍,阻塞IO,非阻塞IO,多路复用IO,异步IO,IO模型比较分析,selectors模块,垃圾回收机制)

    昨日内容回顾 协程实际上是一个线程,执行了多个任务,遇到IO就切换 切换,可以使用yield,greenlet 遇到IO gevent: 检测到IO,能够使用greenlet实现自动切换,规避了IO阻 ...

  9. {python之IO多路复用} IO模型介绍 阻塞IO(blocking IO) 非阻塞IO(non-blocking IO) 多路复用IO(IO multiplexing) 异步IO(Asynchronous I/O) IO模型比较分析 selectors模块

    python之IO多路复用 阅读目录 一 IO模型介绍 二 阻塞IO(blocking IO) 三 非阻塞IO(non-blocking IO) 四 多路复用IO(IO multiplexing) 五 ...

随机推荐

  1. (JavaScript) JS方式调用POS机蓝牙打印

    function print(mac_address) { if (!mac_address) { mui.toast('请选择蓝牙打印机'); return; } main = plus.andro ...

  2. 百度语音合成---前端vue项目

    ☞:官方文档 ☞:网页示例 具体步骤: 1.通过 socket.io 接收后端传过来的数据. 2.判断是否在播放声音.  如果没有则直接获取百度 token 播放声音 3.如果有,则存入数组.声音播放 ...

  3. 一个自动管理项目的Makefile(C语言)

    Linux 是所有嵌入式软件工程师绕不过去的坎, makefile 是在Linux系统中绕不过去的坎. 花了几天时间初步学习和了解了makefile 的作用以及功能,并且制作了一个通用型的makefi ...

  4. u检验粗浅理解

    假设检验是以小概率事件,在一次实验中是不可能发生为前提(事实上是有可能发生的,但不是这样说的话,就落入一个圈,不能继续玩了),来否认原假设. u检验的定义: 已知从正态母体N(u,σ2)中抽得容量为n ...

  5. 【51nod 1251】 Fox序列的数量(以及带限制插板法讲解)

    为什么网上没有篇详细的题解[雾 可能各位聚聚觉得这道题太简单了吧 /kk 题意 首先题目是求满足条件的序列个数,条件为:出现次数最多的数仅有一个 分析 感谢 刚睡醒的 JZ姐姐在咱写题解忽然陷入自闭的 ...

  6. EffectiveJava-1

    最近在看EffectiveJava,记录一下,分享一下自己的心得. 一.将局部变量的作用于最小化 在第一次使用的地方进行声明,过早的声明局部变量,会延长局部变量的生命周期,若在代码块外声明变量,当程序 ...

  7. Python Socket学习之旅(二)

    Socket函数 注解: Socket的close和shutdown--结束数据传输: close-----关闭本进程的socket id,但链接还是开着的,用这个socket id的其它进程还能用这 ...

  8. python面试题2.1:如何实现栈

    本题目摘自<Python程序员面试算法宝典>,我会每天做一道这本书上的题目,并分享出来,统一放在我博客内,收集在一个分类中. 2.1 如何实现栈 [阿里巴巴面试题] 难度系数:⭐⭐⭐ 考察 ...

  9. 微信 AES 解密报错 Illegal key size 三种解决办法

    微信 AES 解密报错 Illegal key size Java 环境 java version "1.8.0_151" Java(TM) SE Runtime Environm ...

  10. 关于html与css的标签及属性(text文本属性、背景background属性、表格标签table、列表、)

    text文本属性1.颜色 colorcolor:red: 2.文本缩进text-indant属性值 num+px text-indant:10px:3.文本修饰 text-decoration属性值: ...