热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Netty源码分析NioEventLoop执行select操作入口

这篇文章主要介绍了Netty源码分析NioEventLoop执行select操作入口,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多

Netty源码分析NioEventLoop执行select操作入口

分析完了selector的创建和优化的过程, 这一小节分析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);
        }
        //代码省略
    }
}

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

1. 轮询io事件

2. 处理轮询到的key

3. 执行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操作入口的详细内容,更多关于Netty分布式NioEventLoop selector操作的资料请关注编程笔记其它相关文章!


推荐阅读
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • IOS开发之短信发送与拨打电话的方法详解
    本文详细介绍了在IOS开发中实现短信发送和拨打电话的两种方式,一种是使用系统底层发送,虽然无法自定义短信内容和返回原应用,但是简单方便;另一种是使用第三方框架发送,需要导入MessageUI头文件,并遵守MFMessageComposeViewControllerDelegate协议,可以实现自定义短信内容和返回原应用的功能。 ... [详细]
  • Monkey《大话移动——Android与iOS应用测试指南》的预购信息发布啦!
    Monkey《大话移动——Android与iOS应用测试指南》的预购信息已经发布,可以在京东和当当网进行预购。感谢几位大牛给出的书评,并呼吁大家的支持。明天京东的链接也将发布。 ... [详细]
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
author-avatar
memeyun916
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有