Netty教程(四)——NioEventLoop执行

https://juejin.cn/post/6888135277685473287

NioEventLoop执行过程在NioEventLoop#run方法里

protected void run() {
    int selectCnt = 0;
    for (;;) {
        try {
            int strategy;
            try {
                strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
                switch (strategy) {
                    //省略其他情况
                    case SelectStrategy.SELECT:
                        //检查是否有io事件
                        long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
                        if (curDeadlineNanos == -1L) {
                            curDeadlineNanos = NONE; // nothing on the calendar
                        }
                        nextWakeupNanos.set(curDeadlineNanos);
                        try {
                            if (!hasTasks()) {
                                //检测io事件的主要逻辑
                                strategy = select(curDeadlineNanos);
                            }
                        } finally {
                            nextWakeupNanos.lazySet(AWAKE);
                        }
                    default:
                }
            } catch (IOException e) {
                //省略
            }

            selectCnt++;
            cancelledKeys = 0;
            needsToSelectAgain = false;
            final int ioRatio = this.ioRatio;
            boolean ranTasks;
            //ioRatio控制处理io事件和处理异步任务队列所用的时间比
            //默认为50,即处理io事件和处理异步任务的队列所用的时间比为1:1
            if (ioRatio == 100) {
                try {
                    if (strategy > 0) {
                        //处理io事件
                        processSelectedKeys();
                    }
                } finally {
                    //处理异步任务队列
                    ranTasks = runAllTasks();
                }
            } else if (strategy > 0) {
                final long ioStartTime = System.nanoTime();
                try {
                    processSelectedKeys();
                } finally {
                    // Ensure we always run tasks.
                    //计算处理io事件所用的时间
                    final long ioTime = System.nanoTime() - ioStartTime;
                    //根据ioRatio计算出处理异步任务队列所能使用的最大时间
                    ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                }
            } else {
                ranTasks = runAllTasks(0); // This will run the minimum number of tasks
            }

            if (ranTasks || strategy > 0) {
                if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS && logger.isDebugEnabled()) {
                    logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
                                 selectCnt - 1, selector);
                }
                selectCnt = 0;
            } else if (unexpectedSelectorWakeup(selectCnt)) { // Unexpected wakeup (unusual case)
                selectCnt = 0;
            }
        } catch (CancelledKeyException e) {
            //省略
        }
        
        //处理关闭的代码,省略
    }
}
复制代码

整个执行过程概括如下

  • 检查是否有io事件(SelectStrategy.SELECT那个情况下面的代码)
  • 处理io事件
  • 处理异步任务队列(外部扔进来的任务)

检测io事件

检测io事件的主要逻辑在case SelectStrategy.SELECT:下如下代码段里

if (!hasTasks()) {
    //检测io事件的主要逻辑
    strategy = select(curDeadlineNanos);
}
复制代码

hasTasks检测异步任务队列里是否还有需要执行的任务,若没有则进入检测io事件的逻辑

下面看看select方法

private int select(long deadlineNanos) throws IOException {
    if (deadlineNanos == NONE) {
        return selector.select();
    }
    // Timeout will only be 0 if deadline is within 5 microsecs
    //计算当前时间到下一个最近的定时任务的时间间隔
    long timeoutMillis = deadlineToDelayNanos(deadlineNanos + 995000L) / 1000000L;
    //如果时间间隔小于等于0,那么进行一个非阻塞的select操作后返回
    //否则进行一个阻塞式的select操作(并且传入本次可以阻塞的最大时间)
    return timeoutMillis <= 0 ? selector.selectNow() : selector.select(timeoutMillis);
}
复制代码

总结一下,主要逻辑是:

  • 若有异步任务就不检测io事件
  • 若无异步任务
    • 若距最近的定时任务的时间间隔小于等于0,则进行一次非阻塞的select操作
    • 若距最近的定时任务的时间间隔大于0,则进行一次阻塞式的select操作(最大阻塞时间为现在到最近的定时任务的时间间隔)

处理io事件

接下来看看processSelectedKeys方法

private void processSelectedKeys() {
    if (selectedKeys != null) {
        processSelectedKeysOptimized();
    } else {
        processSelectedKeysPlain(selector.selectedKeys());
    }
}
复制代码

这里的netty默认会开启一个优化,用自己实现的selectedKeys来替换jdk底层SelectorImpl类里的成员变量selectedKeys,jdk底层的selectedKeys为Hashset类型,而netty的selectedKeys采用数组实现,优化了select操作的复杂度

跟进去看看processSelectedKeysOptimized方法

private void processSelectedKeysOptimized() {
    for (int i = 0; i < selectedKeys.size; ++i) {
        final SelectionKey k = selectedKeys.keys[i];
        // null out entry in the array to allow to have it GC'ed once the Channel close
        // See https://github.com/netty/netty/issues/2363
        selectedKeys.keys[i] = null;

        //之前解析channel初始化的时候说过,netty会把自己的channel
        //当做attachment与jdk底层的channel绑定
        final Object a = k.attachment();

        if (a instanceof AbstractNioChannel) {
            //主要处理逻辑
            processSelectedKey(k, (AbstractNioChannel) a);
        } else {
            @SuppressWarnings("unchecked")
            NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
            processSelectedKey(k, task);
        }

        if (needsToSelectAgain) {
            //省略
        }
    }
}
复制代码

继续跟进看看processSelectedKey方法

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
    final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
    if (!k.isValid()) {
        //省略
    }

    try {
        int readyOps = k.readyOps();
        // We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise
        // the NIO JDK channel implementation may throw a NotYetConnectedException.
        //处理连接事件
        if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
            // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
            // See https://github.com/netty/netty/issues/924
            int ops = k.interestOps();
            ops &= ~SelectionKey.OP_CONNECT;
            k.interestOps(ops);

            unsafe.finishConnect();
        }

        // Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
        //处理写事件
        if ((readyOps & SelectionKey.OP_WRITE) != 0) {
            // Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
            ch.unsafe().forceFlush();
        }

        // Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
        // to a spin loop
        //处理读事件
        if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
            unsafe.read();
        }
    } catch (CancelledKeyException ignored) {
        unsafe.close(unsafe.voidPromise());
    }
}
复制代码

可以看到这里就是在处理连接、读、写事件

处理异步任务队列

接下来看看runAllTasks方法

protected boolean runAllTasks(long timeoutNanos) {
    //从定时任务队列中取出预计开始时间小于或等于当前时间的任务,放到普通任务队列里
    fetchFromScheduledTaskQueue();
    //从普通任务队列里取出一个任务
    Runnable task = pollTask();
    if (task == null) {
        //在处理完所有任务后,执行一些收尾工作
        afterRunningAllTasks();
        return false;
    }

    final long deadline = timeoutNanos > 0 ? ScheduledFutureTask.nanoTime() + timeoutNanos : 0;
    long runTasks = 0;
    long lastExecutionTime;
    for (;;) {
        safeExecute(task);

        runTasks ++;

        //每执行64个任务检测一次是否超过总共允许执行的时间
        //因为nanoTime方法也是比较耗时的,所以不会每次执行任务都检测
        if ((runTasks & 0x3F) == 0) {
            lastExecutionTime = ScheduledFutureTask.nanoTime();
            if (lastExecutionTime >= deadline) {
                break;
            }
        }

        task = pollTask();
        if (task == null) {
            lastExecutionTime = ScheduledFutureTask.nanoTime();
            break;
        }
    }

    afterRunningAllTasks();
    this.lastExecutionTime = lastExecutionTime;
    return true;
}
复制代码

这里简单说一下,netty的任务队列分两类

  • 普通任务队列,这里面的任务需要马上执行
  • 定时任务队列,这些任务按照预计开始时间排序

总结一下,主要做了这几件事

  • 从定时任务队列中取出预计开始时间小于或等于当前时间的任务,放到普通任务队列里
  • 不断从任务队列里取出任务执行
  • 每执行64个任务检测一次是否超过总共允许执行的时间
  • 处理一些收尾任务
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值