18-NioEventLoop实例化过程

谁都会走 提交于 2019-12-16 22:24:33

NioEventLoop实例化过程

  • 本文集合代码流程来分析 NioEventLoop 的实例化过程

  • 开篇我们牢记 NioEventLoop 的两个功能:

1.处理IO事件;执行与 Channel 相关的 IO 操作, 包括调用 select 等待就绪的 IO 事件、读写数据与数
   据的处理等;
2.执行提交的任务;作为任务队列, 执行 taskQueue 中的任务, 例如用户调用 eventLoop.schedule 提
   交的定时任务也是这个线程执行的;

一、构造方法

  • 实例化自然离不开构造方法,不管是我们直接构造对象还是框架内部去构造对象都会走构造方法,因为NioEventLoop 是一个最底层的实现类,继承链有非常多的父类,我们先通过这些父类梳理一下构造方法执行的顺序;

1.1 NioEventLoop

  • 首先是:NioEventLoop,它只有一个构造方法如下:
    /**
     * 构造方法
     */
    NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
                 SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
        
        //1.调用 SingleThreadEventLoop 的构造方法
        super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);
        //参数校验
        if (selectorProvider == null) {
            throw new NullPointerException("selectorProvider");
        }
        if (strategy == null) {
            throw new NullPointerException("selectStrategy");
        }
        //2.SelectorProvider 用于创建 Selector
        provider = selectorProvider;
        //3.创建 Selector 对象,注意内部 通过 SelectorTuple 封装了Selector对象
        final SelectorTuple selectorTuple = openSelector();
        selector = selectorTuple.selector;
        unwrappedSelector = selectorTuple.unwrappedSelector;
        selectStrategy = strategy;
    }

1.2 SingleThreadEventLoop

  • 然后是super调用 SingleThreadEventLoop 的构造方法,因为 SingleThreadEventLoop 比较简单,因此构造方法只是帮助初始化了一个 tailTasks 任务队列属性,super会调用 SingleThreadEventExecutor 的构造方法
    private final Queue<Runnable> tailTasks;

    protected SingleThreadEventLoop(EventLoopGroup parent, Executor executor,
                                    boolean addTaskWakesUp, int maxPendingTasks,
                                    RejectedExecutionHandler rejectedExecutionHandler) {
        super(parent, executor, addTaskWakesUp, maxPendingTasks, rejectedExecutionHandler);
        tailTasks = newTaskQueue(maxPendingTasks);
    }
    
        protected Queue<Runnable> newTaskQueue(int maxPendingTasks) {
        return new LinkedBlockingQueue<Runnable>(maxPendingTasks);
    }

1.3 SingleThreadEventExecutor

  • SingleThreadEventExecutor:SingleThreadEventExecutor的属性比较多,同时会调用父类初始化NioEventLoop 所属的 NioEventLoopGroup
   protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
                                        boolean addTaskWakesUp, int maxPendingTasks,
                                        RejectedExecutionHandler rejectedHandler) {
        //1.初始化所属的 EventLoopGroup
        super(parent);
        //2.是否只在 addTask 的时候唤醒执行线程
        this.addTaskWakesUp = addTaskWakesUp;
        //3.拒绝任务之前的待定线程数量
        this.maxPendingTasks = Math.max(16, maxPendingTasks);
        //4.执行器
        this.executor = ObjectUtil.checkNotNull(executor, "executor");
        //5.待定任务队列
        taskQueue = newTaskQueue(this.maxPendingTasks);
        //6.饱和策略
        rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
    }

1.4 AbstractEventExecutor

  • AbstractEventExecutor:调用 AbstractEventExecutor 初始化 parent 属性,指定EventLoop所属的 EventLoopGroup
    @Override
    public EventExecutorGroup parent() {
        return parent;
    }
  • 到此初始化的过程结束了,下面是两张图来表示初始化的过程:

在这里插入图片描述

在这里插入图片描述

  • 上面两个图都展示了初始化过程中构造方法的调用过程

二、EventLoop 与 Channel 的关联

  • EventLoop 可以管理很多个Channel,并不断轮询这些Channel 所发生的的事件,充当了NIO中select 所在的线程的角色。EventLoop 与 Channel 的关联其实就是 Channel注册到NioEventLoop的过程,我们来看看这个过程。

2.1 Channel注册到EventLoop

  • 在 io.netty.channel.AbstractChannel 的内部类 AbstractUnsafe 中,有 register 注册方法,该方法是将Channel 注册到 EventLoop
        @Override
        public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            //1.EventLoop 参数校验
            if (eventLoop == null) {
                throw new NullPointerException("eventLoop");
            }
            //2.校验未注册,已经注册了就返回
            if (isRegistered()) {
                promise.setFailure(new IllegalStateException("registered to an event loop already"));
                return;
            }
            //3.校验 Channel 和 EventLoop 匹配兼容,isCompatible方法需要子类实现,
            //因为不同类型的 Channel 需要和不同类型的,EventLoop 匹配,子类通常是判断 EventLoop的
            // 类型(二者都是接口,有众多实现)
            if (!isCompatible(eventLoop)) {
                promise.setFailure(new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
                return;
            }

            //4.设置Channel的eventLoop属性,将二者关联起来,其实这就是注册,注册在代码层面的表示很简单,就是在Channel内部有一个成员变量
            //来保存 EventLoop 对象,这就表示该Channel 所注册的 EventLoop,
            AbstractChannel.this.eventLoop = eventLoop;

            //5.在 EventLoop线程 中执行注册逻辑,如果当前线程就在 EventLoop 中,就直接注册,否则就以任务的形式提交给 EventLoop 执
            //行注册,不要忘记 EventLoop 本质是一个线程池,可以接受任务
            if (eventLoop.inEventLoop()) {
                //6.直接注册
                register0(promise);
            } else {
                //7.以人物的形式提交给 EventLoop 注册
                try {
                    eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            System.out.println(Thread.currentThread() + ": register");
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                    //异常处理
                    logger.warn("Force-closing a channel whose registration task was not accepted by an event loop: {}", AbstractChannel.this, t);
                    closeForcibly();
                    closeFuture.setClosed();
                    safeSetFailure(promise, t);
                }
            }
        }
  • 这里需要注意的有两点:一个是注册的代码表示其实简单,一个EventLoop 可以管理N个Channel,Channel内部有一个 EventLoop 的成员属性,它注册到某一个EventLoop,就将这个EventLoop保存到内部的这个属性即可,非常简单;
  • 另一个是注册的任务需要交给对应的 EventLoop 内部的线程执行,因为程序启动的时候这个线程很可能并不是EventLoop内部的线程,如果是就执行,如果不是就将注册的动作以任务的形式提交给EventLoop ,EventLoop自己具备执行任务的功能,后续执行注册即可

2.2 EventLoopGroup注册Channel

EventLoopGroup 的一个重要功能就是将 Channel 注册到 EventLoop,注册动作实际上是由内部的EventLoop完成的。EventLoopGroup会
通过 next 方法选择一个内部的 EventLoop 来执行注册动作(后面会介绍选择策略),最底层本质是在EventLoop的实现子类的内部维护
一个集合,集合内部保存注册在自己身上的 Channel 对象。这里可以简单跟一下源码,可能有点绕。
  • 首先我们看到 EventLoopGroup 的注册功能是在子类 MultithreadEventLoopGroup 实现的,代码如下:
//MultithreadEventLoopGroup 内部选择一个EventLoop来注册
    
    @Override
    public ChannelFuture register(Channel channel) {
        return next().register(channel);
    }
    
    @Override
    public EventLoop next() {
        return (EventLoop) super.next();
    }
  • 在 register 方法打上断点启动后调用栈如下:

在这里插入图片描述

  • 大致的调用流程是:
AbstractBootstrap#bind() ->
    -> AbstractBootstrap#doBind()
        -> AbstractBootstrap#initAndRegister()
            -> MultithreadEventLoopGroup#register 
                -> EventLoopGroup#register(io.netty.channel.Channel)
                    -> SingleThreadEventLoop#register(io.netty.channel.ChannelPromise)
  • 1.首先是启动器的 bind 方法,进入 bind 逻辑,调用 AbstractBootstrap 的 bind 和 doBind 方法
  • 2.然后进入 initAndRegister 方法完成注册逻辑,内部会通过 config 配置对象获取到 EventLoopGroup 对象,调用 EventLoopGroup 的 register 方法
  • 3.往后继续走 MultithreadEventLoopGroup 的register 方法,通过next 获取一个内部的 EventLoop 对象,执行 register,然后就走到了 EventLoop 的register 方法
  • 4.走到 EventLoop 的实现类 SingleThreadEventLoop 的 register 方法
  • 5.继而走到与 SingleThreadEventLoop 所关联的 AbstractChannel 的 register 方法
  • 6.最后注册逻辑走到 AbstractNioChannel 的 register 方法,(核心代码如下):我们看看到注册的代码是不是和NIO几乎一样,形如:channel.register(selector) 返回selectKey,有兴趣可以查阅参考文章[3]的2.2 示例代码
@Override
    protected void doRegister() throws Exception {
        boolean selected = false;
        for (; ; ) {
            try {
                //这段代码,是不是和NIO代码很相似
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                return;
            } catch (CancelledKeyException e) {
                // TODO TODO 1003 doRegister 异常
                if (!selected) {
                    // Force the Selector to select now as the "canceled" SelectionKey may still be
                    // cached and not removed because no Select.select(..) operation was called yet.
                    eventLoop().selectNow();
                    selected = true;
                } else {
                    // We forced a select operation on the selector before but the SelectionKey is still cached
                    // for whatever reason. JDK bug ?
                    throw e;
                }
            }
        }
    }
  • 集合2.1 和 2.2,就把整个 Channel 注册到 NioEventLoop 的流程梳理了,2.1是NioEventLoop的实现部分,2.2是NioEventLoopGroup的实现部分,NioEventLoopGroup本质还是调用内部的 NioEventLoop来完成Channel 到 NioEventLoop的注册
  • 整个过程:
启动器Bootstrap#bind -> EventLoopGroup#register -> EventLoop#register -> Channel内部类AbstractUnsafe#register方法注册 -> 改变内部的属性 -> 注册完成
  • 简易流程图如下:

在这里插入图片描述

三、EventLoop启动

  • 通过前面的两个步骤,我们知道了 EventLoop 的构造基本流程和Channel注册到 EventLoop 的过程,但是 EventLoop 作为一个处理 Channel 事件的线程,什么时候启动呢?
  • 之前我们有说过 NioEventLoop 内部持有一个线程,NioEventLoop的启动就是这个线程的启动,这个线程对象保存在 SingleThreadEventExecutor,他的启动在 SingleThreadEventExecutor#startThread 方法

3.1 SingleThreadEventExecutor#startThread

    //io.netty.util.concurrent.SingleThreadEventExecutor#startThread
    
    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);
                }
            }
        }
    }
  • doStartThread 执行逻辑主体
    /**
     * 启动 EventExecutor 内部的线程(如果是EventLoop实现类,启动的就是EventLoop内部的线程)
     */
    private void doStartThread() {
        assert thread == null;
        //1.使用启动器执行一个任务
        executor.execute(new Runnable() {

            @Override
            public void run() {
                //2.新启动的线程就是当前 EventExecutor 内部的线程
                thread = Thread.currentThread();

                //3.如果当前线程已经被标记打断,则进行打断操作。
                if (interrupted) {
                    thread.interrupt();
                }

                //4.是否执行成功的标记
                boolean success = false;

                //5.更新最后执行时间
                updateLastExecutionTime();
                try {
                    //6.执行任务,这里会启动EventLoop,注意这里走的是run,而不是start方法
                    //这里仅仅只是一个普通的run启动而已,因为这里已经是在 EventExecutor 对应的
                    //线程里面执行了,这里面对应的 NioEventLoop实现就是死循环监听Channel 的事件
                    SingleThreadEventExecutor.this.run();

                    //7.标记执行成功
                    success = true;
                } catch (Throwable t) {
                    logger.warn("Unexpected exception from an event executor: ", t);
                } finally {
                    //8.优雅关闭
                    for (; ; ) {
                        int oldState = state;
                        if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet(
                                SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) {
                            break;
                        }
                    }

                    // Check if confirmShutdown() was called at the end of the loop.
                    if (success && gracefulShutdownStartTime == 0) {
                        if (logger.isErrorEnabled()) {
                            logger.error("Buggy " + EventExecutor.class.getSimpleName() + " implementation; " +
                                    SingleThreadEventExecutor.class.getSimpleName() + ".confirmShutdown() must " +
                                    "be called before run() implementation terminates.");
                        }
                    }

                    //关闭前确认,执行完剩余任务
                    try {
                        // Run all remaining tasks and shutdown hooks.
                        for (; ; ) {
                            if (confirmShutdown()) {
                                break;
                            }
                        }
                    } finally {
                        try {
                            // 清理,释放资源
                            cleanup();
                        } finally {
                            STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED);
                            threadLock.release();
                            if (!taskQueue.isEmpty()) {
                                if (logger.isWarnEnabled()) {
                                    logger.warn("An event executor terminated with " +
                                            "non-empty task queue (" + taskQueue.size() + ')');
                                }
                            }

                            terminationFuture.setSuccess(null);
                        }
                    }
                }
            }
        });
    }

3.2 NioEventLoop#run

  • 我们看到 doStartThread 里面会启动 EventLoop 内部的线程,并且会做一系列的线程状态设置,以及关闭后的资源清理工作,而 NioEventLoop 子类则将需要做的在run 方法中重写,如下:
    /**
     * NioEventLoop 的主体循环逻辑,一个死循环,不断的处理 Channel事件,类似于NIO中的while(true){ ... } 死循环
     */
    @Override
    protected void run() {
        //1.死循环,处理注册在自己身上的Channel 的事件
        for (; ; ) {
            try {
                //2.有任务就执行selectNow,没有任务就返回-1
                switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                    //3.默认实现下,不存在这个情况。
                    case SelectStrategy.CONTINUE:
                        continue;
                        //4.SELECT值为-1,返回-1表示hasTasks为false,没有任务
                    case SelectStrategy.SELECT:
                        // 重置 wakenUp 标记为 false
                        // 选择( 查询 )任务
                        select(wakenUp.getAndSet(false));

                        // 'wakenUp.compareAndSet(false, true)' is always evaluated
                        // before calling 'selector.wakeup()' to reduce the wake-up
                        // overhead. (Selector.wakeup() is an expensive operation.)
                        //
                        // However, there is a race condition in this approach.
                        // The race condition is triggered when 'wakenUp' is set to
                        // true too early.
                        //
                        // 'wakenUp' is set to true too early if:
                        // 1) Selector is waken up between 'wakenUp.set(false)' and
                        //    'selector.select(...)'. (BAD)
                        // 2) Selector is waken up between 'selector.select(...)' and
                        //    'if (wakenUp.get()) { ... }'. (OK)
                        //
                        // In the first case, 'wakenUp' is set to true and the
                        // following 'selector.select(...)' will wake up immediately.
                        // Until 'wakenUp' is set to false again in the next round,
                        // 'wakenUp.compareAndSet(false, true)' will fail, and therefore
                        // any attempt to wake up the Selector will fail, too, causing
                        // the following 'selector.select(...)' call to block
                        // unnecessarily.
                        //
                        // To fix this problem, we wake up the selector again if wakenUp
                        // is true immediately after selector.select(...).
                        // It is inefficient in that it wakes up the selector for both
                        // the first case (BAD - wake-up required) and the second case
                        // (OK - no wake-up required).

                        // 唤醒。原因,见上面中文注释
                        if (wakenUp.get()) {
                            selector.wakeup();
                        }
                        // fall through
                    default:
                }

                cancelledKeys = 0;
                needsToSelectAgain = false;

                //ioRatio表示期望的线程处理IO事件的时间比例
                final int ioRatio = this.ioRatio;
                
                //既然期望是100%,那么久一直处理IO任务
                if (ioRatio == 100) {
                    try {
                        //处理 Channel 感兴趣的就绪 IO 事件
                        processSelectedKeys();
                    } finally {
                        // 运行所有普通任务和定时任务,不限制时间
                        // Ensure we always run tasks.
                        runAllTasks();
                    }
                    //如果不是100%,那么就指定 runAllTasks 的运行时间
                } else {
                    final long ioStartTime = System.nanoTime();
                    try {
                        processSelectedKeys();
                    } finally {
                        // 运行所有普通任务和定时任务,限制时间
                        // Ensure we always run tasks.
                        final long ioTime = System.nanoTime() - ioStartTime;
                        runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                    }
                }
            } catch (Throwable t) {
                handleLoopException(t);
            }
            //优雅关闭,如果抛出来异常,就会处理 shutdown
            // Always handle shutdown even if the loop processing threw an exception.
            try {
                if (isShuttingDown()) {
                    closeAll();
                    if (confirmShutdown()) {
                        return;
                    }
                }
            } catch (Throwable t) {
                handleLoopException(t);
            }
        }
    }
  • 对比NIO的代码 (NIO代码可以参考 03-BIO、NIO到Netty ),我们看到这里很相似,在一个死循环中通过selectStrategy去监听事件,但是我们只看到一点影子,我们姑且认为 switch 逻辑在获取事件,方法后面部分则是根据 ioRatio 配置来处理IO事件,这里 switch 里面的 select 内部还有很多逻辑先不展开,下一篇文章:《Netty 的 IO事件处理》再开展这部分的内容;

四、小结

  • 本文主要梳理了 NioEventLoop 的实例化过程,其中包括构造方法执行流程,Channel 注册到 NioEventLoop 的流程,以及 NioEventLoop 启动后的任务执行过程,但是并未深入了解任务执行过程中对事件和任务的处理细节,后文针对 NioEventLoop 的IO事件处理开展。
  • 下一篇文章:《NioEventLoop IO事件处理》继续;
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!