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整个服务流程就分析的差不多了,涉及到的源码非常多,建议读者多读几遍源码