我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Netty源码分析NioEventLoop怎么执行select

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Netty源码分析NioEventLoop怎么执行select

本篇内容主要讲解“Netty源码分析NioEventLoop怎么执行select”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Netty源码分析NioEventLoop怎么执行select”吧!

select操作的入口,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);        }        //代码省略    }}

代码比较长, 其实主要分为三部分:

轮询io事件

处理轮询到的key

执行task

这一小节, 主要剖析第一部分

轮询io事件

首先switch块中默认会走到SelectStrategy.SELECT中, 执行select(wakenUp.getAndSet(false))方法

参数wakenUp.getAndSet(false)代表当前select操作是未唤醒状态

进入到select(wakenUp.getAndSet(false))方法中

private void select(boolean oldWakenUp) throws IOException {    Selector selector = this.selector;    try {        int selectCnt = 0;        //当前系统的纳秒数        long currentTimeNanos = System.nanoTime();        //截止时间=当前时间+队列第一个任务剩余执行时间        long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);        for (;;) {            //阻塞时间(毫秒)=(截止时间-当前时间+0.5毫秒)            long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;            if (timeoutMillis <= 0) {                if (selectCnt == 0) {                    selector.selectNow();                    selectCnt = 1;                }                break;            }            if (hasTasks() && wakenUp.compareAndSet(false, true)) {                selector.selectNow();                selectCnt = 1;                break;            }            //进行阻塞式的select操作            int selectedKeys = selector.select(timeoutMillis);            //轮询次数            selectCnt ++;            //如果轮询到一个事件(selectedKeys != 0), 或者当前select操作需要唤醒(oldWakenUp),             //或者在执行select操作时已经被外部线程唤醒(wakenUp.get()),             //或者任务队列已经有任务(hasTask), 或者定时任务队列中有任务(hasScheduledTasks())            if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {                break;            }            //省略            //记录下当前时间            long time = System.nanoTime();            //当前时间-开始时间>=超时时间(条件成立, 执行过一次select操作, 条件不成立, 有可能发生空轮询)            if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {                //代表已经进行了一次阻塞式select操作, 操作次数重置为1                selectCnt = 1;            } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {                //省略日志代码                //如果空轮询的次数大于一个阈值(512), 解决空轮询的bug                rebuildSelector();                selector = this.selector;                selector.selectNow();                selectCnt = 1;                break;            }            currentTimeNanos = time;        }        //代码省略    } catch (CancelledKeyException e) {        //省略    }}

首先通过 long currentTimeNanos = System.nanoTime() 获取系统的纳秒数

继续往下看:

long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);

delayNanos(currentTimeNanos)代表距定时任务中第一个任务剩余多长时间, 这个时间+当前时间代表这次操作不能超过的时间, 因为超过之后定时任务不能严格按照预定时间执行, 其中定时任务队列是已经按照执行时间有小到大排列好的队列, 所以第一个任务则是最近需要执行的任务, selectDeadLineNanos就代表了当前操作不能超过的时间

然后就进入到了无限for循环

for循环中我们关注:

long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L

selectDeadLineNanos - currentTimeNanos+500000L 代表截止时间-当前时间+0.5毫秒的调整时间, 除以1000000表示将计算的时间转化为毫秒数

最后算出的时间就是selector操作的阻塞时间, 并赋值到局部变量的timeoutMillis中

后面有个判断 if(imeoutMillis<0) , 代表当前时间已经超过了最后截止时间+0.5毫秒,  selectCnt == 0 代表没有进行select操作, 满足这两个条件, 则执行selectNow()之后, 将selectCnt赋值为1之后跳出循环

如果没超过截止时间, 就进行了 if(hasTasks() && wakenUp.compareAndSet(false, true)) 判断

这里我们关注hasTasks()方法, 这里是判断当前NioEventLoop所绑定的taskQueue是否有任务, 如果有任务, 则执行selectNow()之后, 将selectCnt赋值为1之后跳出循环(跳出循环之后去执行任务队列中的任务)

hasTasks()方法可以自己跟一下, 非常简单

如果没有满足上述条件, 就会执行 int selectedKeys = selector.select(timeoutMillis) 进行阻塞式轮询, 并且自增轮询次数, 而后会进行如下判断:

if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {    break;}

selectedKeys != 0代表已经有轮询到的事件, oldWakenUp代表当前select操作是否需要唤醒, wakenUp.get()说明已经被外部线程唤醒, hasTasks()代表任务队列是否有任务, hasScheduledTasks()代表定时任务队列是否任务, 满足条件之一, 就跳出循环

long time = System.nanoTime() 记录了当前的时间, 之后有个判断:

 if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) 这里的意思是当前时间-阻塞时间>方法开始执行的时间, 这里说明已经完整的执行完成了一个阻塞的select()操作, 将selectCnt设置成1

如果此条件不成立, 说明没有完整执行select()操作, 可能触发了一次空轮询, 根据前一个selectCnt++这步我们知道, 每触发一次空轮询selectCnt都会自增

之后会进入第二个判断 SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD 

其中SELECTOR_AUTO_REBUILD_THRESHOLD默认是512, 这个判断意思就是空轮询的次数如果超过512次, 则会认为是发生了epoll bug, 这样会通过rebuildSelector()方法重新构建selector, 然后将重新构建的selector赋值到局部变量selector, 执行一次selectNow(), 将selectCnt初始化1, 跳出循环

rebuildSelector()方法中, 看netty是如何解决epoll bug的

public void rebuildSelector() {    //是否是由其他线程发起的    if (!inEventLoop()) {        //如果是其他线程发起的, 将rebuildSelector()封装成任务队列, 由NioEventLoop进行调用        execute(new Runnable() {            @Override            public void run() {                rebuildSelector();            }        });        return;    }    final Selector oldSelector = selector;    final Selector newSelector;    if (oldSelector == null) {        return;    }    try {        //重新创建一个select        newSelector = openSelector();    } catch (Exception e) {        logger.warn("Failed to create a new Selector.", e);        return;    }    int nChannels = 0;    for (;;) {        try {            //拿到旧select中所有的key            for (SelectionKey key: oldSelector.keys()) {                Object a = key.attachment();                try {                    Object a = key.attachment();                    //代码省略                    //获取key注册的事件                    int interestOps = key.interestOps();                    //将key注册的事件取消                    key.cancel();                    //注册到重新创建的新的selector中                    SelectionKey newKey = key.channel().register(newSelector, interestOps, a);                    //如果channel是NioChannel                    if (a instanceof AbstractNioChannel) {                        //重新赋值                        ((AbstractNioChannel) a).selectionKey = newKey;                    }                    nChannels ++;                } catch (Exception e) {                    //代码省略                }            }        } catch (ConcurrentModificationException e) {            continue;        }        break;    }    selector = newSelector;    //代码省略}

首先会判断是不是当前NioEventLoop线程执行的, 如果不是, 则将构建方法封装成task由当前NioEventLoop执行

 final Selector oldSelector = selector 表示拿到旧的selector

然后通过 newSelector = openSelector() 创建新的selector

通过for循环遍历所有注册在selector中的key

 Object a = key.attachment() 是获取channel, 第一章讲过, 在注册时, 将自身作为属性绑定在key上

for循环体中, 通过 int interestOps = key.interestOps() 获取其注册的事件

key.cancel()将注册的事件进行取消

 SelectionKey newKey = key.channel().register(newSelector, interestOps, a) 将channel以及注册的事件注册在新的selector中

 if (a instanceof AbstractNioChannel) 判断是不是NioChannel

如果是NioChannel, 则通过 ((AbstractNioChannel) a).selectionKey = newKey 将自身的属性selectionKey赋值为新返回的key

 selector = newSelector 将自身NioEventLoop属性selector赋值为新创建的newSelector

至此, 就是netty解决epoll bug的步骤, 其实就是创建一个新的selector, 将旧selector中注册的channel和事件重新注册到新的selector中, 然后将自身selector属性替换成新创建的selector

到此,相信大家对“Netty源码分析NioEventLoop怎么执行select”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Netty源码分析NioEventLoop怎么执行select

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Netty源码分析NioEventLoop怎么执行select

本篇内容主要讲解“Netty源码分析NioEventLoop怎么执行select”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Netty源码分析NioEventLoop怎么执行select”吧!
2023-06-29

Flink执行流程与源码分析

整体的流程与架构可能三两张图或者三言两语就可以勾勒出画面,但是背后源码的实现是艰辛的。源码的复杂度和当初设计框架的抓狂感,我们只有想象。现在我们只是站在巨人的肩膀上去学习。

MySQL源码分析之SQL函数执行

SQL_PARSE: SQL解析生成AST语法的语法树,to_char函数,实际中已解析为Item_func_to_char的语法树节点。SQL_RESOLVER: 准备阶段prepare,初始化赋值,如Item_func_to_char:
SQL函数语法2024-12-01

【Mybatis源码解析】mapper实例化及执行流程源码分析

文章目录 简介 环境搭建 源码解析 附 基础环境:JDK17、SpringBoot3.0、mysql5.7 储备知识:《【Spring6源码・AOP】AOP源码解析》、《JDBC详细
2023-08-20

怎么进行ActionInvoker源码分析

本篇内容介绍了“怎么进行ActionInvoker源码分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!throw new RenderTe
2023-06-17

Appium Android Bootstrap源码分析之命令解析执行

通过上一篇文章《Appium Android Bootstrap源码分析之控件AndroidElement》我们知道了Appium从pc端发送过来的命令如果是控件相关的话,终目标控件在bootstrap中是以AndroidElement对象
2022-06-06

怎么进行FileZilla源代码分析

怎么进行FileZilla源代码分析,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。FileZilla是一种快速、可信赖的FTP客户端以及服务器端开放源代码程式,具有多种特色
2023-06-16

Spring@Bean注解深入分析源码执行过程

随着SpringBoot的流行,我们现在更多采用基于注解式的配置从而替换掉了基于XML的配置,所以本篇文章我们主要探讨基于注解的@Bean以及和其他注解的使用
2023-01-10

SpringCloud Gateway远程命令执行漏洞源码分析

这篇文章主要讲解了“SpringCloud Gateway远程命令执行漏洞源码分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“SpringCloud Gateway远程命令执行漏洞源码分析
2023-07-05

Disruptor广播模式与执行顺序链源码分析

消费者线程起来后,然后进入死循环,持续不断从生产者处批量获取可用的序号,如果获取到可用序号后,那么遍历所有可用序号,然后调用eventHandler的onEvent方法消费数据,onEvent方法写的是消费者的业务逻辑。

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录