netty之EventLoop源码分析

我们在讲解服务端和客户端时经常会看到提交一个任务到channel对应的EventLoop上,后续的io事件监听和任务执行都在EventLoop完成,可以说EventLoop是netty最核心的组件,我们接下来一一分析 剥开这层神秘的面纱

提交一个连接任务异步执行

channel.eventLoop().execute(new Runnable() {
    @Override
    public void run() {
        if (localAddress == null) {
            channel.connect(remoteAddress, connectPromise);
        } else {
            channel.connect(remoteAddress, localAddress, connectPromise);
        }
        connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
    }
});
/***************************SingleThreadEventExecutor************************/
public void execute(Runnable task) {
    if (task == null) {
        throw new NullPointerException("task");
    }
    // 判断提交任务的线程是否是EventLoop线程
    boolean inEventLoop = inEventLoop();
    // 把任务提交到队列中
    addTask(task);
    if (!inEventLoop) {
        // 如果不是 尝试开启EventLoop线程
        startThread();
        if (isShutdown() && removeTask(task)) {
            reject();
        }
    }
    if (!addTaskWakesUp && wakesUpForTask(task)) {
        // 唤醒正在等待task阻塞的线程,实现在NioEventLoop中,阻塞对象为selector
        wakeup(inEventLoop);
    }
}

private void startThread() {
    // 如果线程还未开启,随即开启线程
    if (state == ST_NOT_STARTED) {
        if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
            try {
                doStartThread();
            } catch (Throwable cause) {
                STATE_UPDATER.set(this, ST_NOT_STARTED);
                PlatformDependent.throwException(cause);
            }
        }
    }
}

private void doStartThread() {
    assert thread == null;
    // 提交一个任务用来开启"EventLoop线程",这里终于用到了executor线程池
    executor.execute(new Runnable() {
        @Override
        public void run() {
            thread = Thread.currentThread();
            if (interrupted) {
                thread.interrupt();
            }
            try {
                // !!!终于要干正事了
                SingleThreadEventExecutor.this.run();
                success = true;
            }
        }
    });
}


/***************************NioEventLoop************************/
protected void run() {
    // 无限循环
    for (;;) {
        try {
            // selectNoSupplier执行的是selector.selectNow(), hasTasks() 判断任务队列是否有任务
            // 如果不为空 那就执行selector.selectNow()返回结果,如果为空返回SelectStrategy.SELECT
            // 目的根据是否有任务来决定阻塞
            switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                case SelectStrategy.CONTINUE:
                    continue;
                case SelectStrategy.SELECT:
                    // 队列中没有任务进行等待io阻塞
                    select(wakenUp.getAndSet(false));
                    if (wakenUp.get()) {
                        // 唤醒来自其它线程执行selector.select()的阻塞 因为当前已经获取到最新的io事件了
                        selector.wakeup();
                    }
                default:
            }

            cancelledKeys = 0;
            needsToSelectAgain = false;
            final int ioRatio = this.ioRatio;
            if (ioRatio == 100) {
                try {
                    // 执行io事件
                    processSelectedKeys();
                } finally {
                    // 执行队列任务
                    runAllTasks();
                }
            } else {
                final long ioStartTime = System.nanoTime();
                try {
                    processSelectedKeys();
                } finally {
                    final long ioTime = System.nanoTime() - ioStartTime;
                    // 通过ioRatio和执行io耗费的时长来算出能执行队列任务的时长
                    runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                }
            }
        } catch (Throwable t) {
            handleLoopException(t);
        }
        try {
            if (isShuttingDown()) {
                closeAll();
                if (confirmShutdown()) {
                    return;
                }
            }
        } catch (Throwable t) {
            handleLoopException(t);
        }
    }
}

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.5ms进行阻塞 默认阻塞1s
            long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
            if (timeoutMillis <= 0) {
                if (selectCnt == 0) {
                    selector.selectNow();
                    selectCnt = 1;
                }
                break;
            }
            // 如果恰好在添加task时,wakenUp被设置了true 也就是需要进行唤醒 那么这个任务是不需要现在执行的,如果不这样做,任务可能会被挂起,直到select操作超时。
            if (hasTasks() && wakenUp.compareAndSet(false, true)) {
                // 不需要唤醒 再执行一次非阻塞的selectNow()随即返回
                selector.selectNow();
                selectCnt = 1;
                break;
            }
            
            int selectedKeys = selector.select(timeoutMillis);
            selectCnt ++;

            // 如果1s后有返回||select()被唤醒||队列中有任务||有定时任务 则跳出循环
            if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
                break;
            }

            long time = System.nanoTime();
            if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
                selectCnt = 1;
            } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
                    selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
                // 当前循环时间小于1s且循环次数超过selector重新生成的界限 将当前selector重新生成一个,将旧selector对应的channel也进行重新注册
                rebuildSelector();
                selector = this.selector;
                selector.selectNow();
                selectCnt = 1;
                break;
            }
            currentTimeNanos = time;
        }
    } 
}

首先根据队列中是有任务来决定执行带阻塞的selector.select()还是不带阻塞的select.selectNow(),直到

然后执行io事件和队列任务,netty在这里做了一个优化策略,通过ioRatio来控制 执行任务队列的时长,为了防止任务队列一直运行阻塞着影响后续的io事件处理,ioRatio默认为50也就是执行队列任务的时长等于io事件执行的时间

processSelectedKeys()和runAllTasks()方法比较简单,直接追进去一看就明白了了,这里我就不贴了

经过本节分析后,netty整个服务流程就分析的差不多了,涉及到的源码非常多,建议读者多读几遍源码

posted @ 2020-12-28 17:15  努力工作的小码农  阅读(306)  评论(0编辑  收藏  举报