Netty源码分析系列之NioEventLoop的执行流程

本文详细分析了Netty的NioEventLoop在run()方法中的执行流程,包括轮询事件、处理网络IO、执行任务等关键步骤。Netty通过selectStrategy计算选择合适的轮询方法,并通过ioRatio控制IO处理和任务执行的时间比例。文章还介绍了Netty如何避免JDK的空轮询BUG,当CPU空转次数超过阈值时,Netty会重建Selector,降低空轮询发生的概率。最后,文章总结了NioEventLoop的主要工作,并推荐了相关Netty源码分析文章。
摘要由CSDN通过智能技术生成

扫描下方二维码或者微信搜索公众号菜鸟飞呀飞,即可关注微信公众号,阅读更多Spring源码分析Java并发编程文章。

微信公众号

1.前言

在上一篇文章中分析了NioEventLoop的创建以及启动过程的源码,在文章结尾处提到,当NioEventLoop线程启动以后,会一直在一个无限 for 循环中一直循环,至死方休,那么在循环中,NioEventLoop到底在循环处理什么呢?这将是本文分析的重点。同时,还是先思考一下以下两个问题。

  1. 众所周知,Netty 巧妙的避免了 JDK 的空轮询的 BUG,那么什么是 JDK 的空轮询 BUG?
  2. Netty 又是如何避免的?

2.NioEventLoop.run()

在 NioEventLoop 通过 doStartThread()启动线程后,会执行这一行代码。这一行代码最终会调用的是 NioEventLoop 的 run()方法。

SingleThreadEventExecutor.this.run();

run()方法的源码很长,但主要逻辑主要分为三部分,第一部分:通过调用 select()从操作系统中轮询到网络 IO 事件;第二部分:处理 IO 事件;第三部分:处理 nioEventLoop 的任务队列中的普通任务和定时任务。我对 run()方法的源码进行了删减,精简后的源码如下。

run()方法源码

在 run()方法中,会通过选择策略(selectStrategy )来计算 switch 语句中的条件值,在计算的时候,会先通过 hasTasks() 方法来判断 taskQueue 和 tailQueue 中是否有任务等待被执行,如果有任务,则将调用 selectNow()方法从操作系统中来轮询网络 IO 事件;如果没有任务,则将调用 select(timeout)方法来轮询网络 IO 事件。

为什么要这样做呢?因为 Netty 中为了保证任务被及时执行,selectNow()方法是个非阻塞方法,如果操作系统中没有已经准备好的网络 IO 事件,那么就会立即返回,有已经准备好的网络 IO 事件,那么就会将这些网络 IO 事件查询出来并立马返回。而 select(timeout)方法也是从操作系统中轮询网络 IO 事件,但是它是一个阻塞方法,当 netty 中有任务等待被执行时,使用阻塞方法,显然会造成任务被执行不及时的问题。

如果selectStrategy计算出来的值为-1,那么就会执行到下面这一行代码。

select(wakenUp.getAndSet(false))

这行代码首先会将 wakenUp 的值置为 false。wakenUp 字段表示的含义是是否需要唤醒 selector,在每次进行新的轮询时,都会将 wakenUp 设置为 false。然后调用 select()方法,从操作系统中轮询出来网络 IO 事件。

接着在 run()方法中会对 ioRatio 的值进行判断,ioRatio 的含义又是什么呢?在 Netty 中,NioEventLoop 每一次循环其实主要干两类事,一是处理网络 IO 事件,二是执行任务(包括普通任务和定时任务),但是处理这两类任务的所消耗的时间是不一样的。而且有些系统可能期望分配给处理网络 IO 事件的时间多一点,有些系统可能期望分配给处理任务的时间多一些,那么 netty 就需要提供一个变量来控制执行这两类事的所花的时间的占比,这个变量就是 ioRatio,翻译过来就是 IO 的时间占比。默认情况下,ioRatio 的值为 50,即处理网络 IO 的时间和处理任务的时间各占一半。所以默认情况下,会进入到 else 语句块中,在 else 语句块中,先进行了网络 IO 的处理(processSelectedKeys()),然后进行任务的处理(runAllTasks(timeoutNanos))。

接下来将对 run()方法中的三个主要部分进行详细分析。

3.轮询事件(select())

如果selectStrategy计算出来的值为-1,那么就会执行 select(oldWakenUp)方法。该方法的源码又是很长,为了方便阅读,我进行了删减,其中省略了解决 JDK 空轮询 bug 相关的代码,这一部分代码会在后面详细说明。精简后的源码如下。

private void select(boolean oldWakenUp) throws IOException {
   
    Selector selector = this.selector;
    try {
   
        int selectCnt = 0;
        long currentTimeNanos = System.nanoTime();
        // 当定时任务队列中没有任务时,select操作的截止时间为: 当前时间 + 1秒
        long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);

        for (;;) {
   
            // 1.定时任务截止时间快到了(<0.5ms),就跳出循环
            long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
            if (timeoutMillis <= 0) {
   
                // 如果还没有进行过select()操作,就执行一次selectNow()操作,selectNow()方法不会阻塞线程。
                // 如果进行过select()操作,就直接跳出循环
                if (selectCnt == 0) {
   
                    selector.selectNow();
                    selectCnt = 1;
                }
                break;
            }
            // 轮询过程中,可能会有新的任务加入到任务队列中,就中断本次轮询,并将wakeUp设置为true
            // 为什么呢?因为netty为了保证任务能够及时执行
            if (hasTasks() && wakenUp.compareAndSet(false, true)) {
   
                selector.selectNow();
     
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值