Netty源码之EventLoop

关于线程池,池化和重用相对于简单地为每个任务都创建和销毁线程是一种进步,但它并不能消除由于上下文切换所带来的开销。下面我们来看看netty的线程模型,它是如何优化和简化多线程的操作。

netty常用时,配置NioEventLoopGroup,为每个channel绑定相应的EventLoop,EventLoop上绑定的Thread处理相应channel上的所有io操作和事件。

NioEventLoopGroup的构造方法具体逻辑实际上是在其超类MultithreadEventExecutorGroup上

    protected MultithreadEventExecutorGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
        this.terminationFuture = new DefaultPromise(GlobalEventExecutor.INSTANCE);
        if(nThreads <= 0) {
            throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", new Object[]{Integer.valueOf(nThreads)}));
        } else {
            if(threadFactory == null) {
                threadFactory = this.newDefaultThreadFactory();
            }

            this.children = new SingleThreadEventExecutor[nThreads];

            int j;
            for(int i = 0; i < nThreads; ++i) {
                boolean success = false;
                boolean var17 = false;

                try {
                    var17 = true;
                    this.children[i] = this.newChild(threadFactory, args);
                    success = true;
                    var17 = false;
                } catch (Exception var18) {
                    throw new IllegalStateException("failed to create a child event loop", var18);
                } finally {
                    if(var17) {
                        if(!success) {
                            int j;
                            for(j = 0; j < i; ++j) {
                                this.children[j].shutdownGracefully();
                            }

                            for(j = 0; j < i; ++j) {
                                EventExecutor e = this.children[j];

                                try {
                                    while(!e.isTerminated()) {
                                        e.awaitTermination(2147483647L, TimeUnit.SECONDS);
                                    }
                                } catch (InterruptedException var19) {
                                    Thread.currentThread().interrupt();
                                    break;
                                }
                            }
                        }

                    }
                }

                if(!success) {
                    for(j = 0; j < i; ++j) {
                        this.children[j].shutdownGracefully();
                    }

                    for(j = 0; j < i; ++j) {
                        EventExecutor e = this.children[j];

                        try {
                            while(!e.isTerminated()) {
                                e.awaitTermination(2147483647L, TimeUnit.SECONDS);
                            }
                        } catch (InterruptedException var21) {
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                }
            }

            FutureListener<Object> terminationListener = new FutureListener<Object>() {
                public void operationComplete(Future<Object> future) throws Exception {
                    if(MultithreadEventExecutorGroup.this.terminatedChildren.incrementAndGet() == MultithreadEventExecutorGroup.this.children.length) {
                        MultithreadEventExecutorGroup.this.terminationFuture.setSuccess((Object)null);
                    }

                }
            };
            EventExecutor[] arr$ = this.children;
            j = arr$.length;

            for(int i$ = 0; i$ < j; ++i$) {
                EventExecutor e = arr$[i$];
                e.terminationFuture().addListener(terminationListener);
            }

        }
    }

在构造方法中,先保证nThread大于0,它就是EventLoopGroup中线程的数量。主要是创建SingleThreadEventExecutor数组,即存放单个线程的容器,它的大小即为nThread,具体赋值是调用抽象方法newChild(),实现在NioEventLoopGroup中

    @Override
    protected EventExecutor newChild(
            ThreadFactory threadFactory, Object... args) throws Exception {
        return new NioEventLoop(this, threadFactory, (SelectorProvider) args[0]);
    }

NioEventLoop即是继承自SingleThreadEventExecutor

    NioEventLoop(NioEventLoopGroup parent, ThreadFactory threadFactory, SelectorProvider selectorProvider) {
        //调用父类的构造函数
        super(parent, threadFactory, false);
        //参数检查
        if (selectorProvider == null) {
            throw new NullPointerException("selectorProvider");
        }
        provider = selectorProvider;
        selector = openSelector();
    }

调用父类构造函数,再打开selector。通过回溯NioEventLoop到NioEventLoopGroup构造函数中,我们知道这里的provider是WindowsSelectorProvider类的openSelector()

    public AbstractSelector openSelector() throws IOException {
        return new WindowsSelectorImpl(this);
    }

WindowsSelectorImpl是jdk nio部分的选择器。

继承链上的SingleThreadEventExector给出更详细的构造方法

protected SingleThreadEventExecutor(EventExecutorGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp) {
        this.terminationFuture = new DefaultPromise(GlobalEventExecutor.INSTANCE);
        if(threadFactory == null) {
            throw new NullPointerException("threadFactory");
        } else {
            this.parent = parent;
            this.addTaskWakesUp = addTaskWakesUp;
            this.thread = threadFactory.newThread(new Runnable() {
                public void run() {
                    boolean success = false;
                    SingleThreadEventExecutor.this.updateLastExecutionTime();
                    boolean var274 = false;

                    label2893: {
                        try {
                            var274 = true;
                            SingleThreadEventExecutor.this.run();
                            success = true;
                            var274 = false;
                            break label2893;
                        } catch (Throwable var296) {
                            SingleThreadEventExecutor.logger.warn("Unexpected exception from an event executor: ", var296);
                            var274 = false;
                        } finally {
                        ......
         this.taskQueue = this.newTaskQueue();
        }
    }

实现过长。。也没太看懂。明显可以看到,在这里生成了个线程赋值给thread,也就是每个eventLoop都与一个thread绑定,生命周期同步。每个对应channel的io跟事件处理线程,就是这个thread成员,在这里直接调用了该执行器的抽象方法run(),跟踪代码后发现,就是执行NioEventLoop的run()

最后,创建任务队列LinkedBlockingQueue作为正在等待执行的task队列。

接下来看下eventloop的run方法,算是核心

 protected void run() {
        for (; ; ) {
            //得到旧值赋值给oldWakenUp,并给wakenUp赋值为false
            oldWakenUp = wakenUp.getAndSet(false);
            try {
                if (hasTasks()) {
                    selectNow();
                } else {
                    select();

                    if (wakenUp.get()) {
                        selector.wakeup();
                    }
                }

                cancelledKeys = 0;
                //得到执行线程执行IO开始时间,单位毫秒
                final long ioStartTime = System.nanoTime();
                needsToSelectAgain = false;

                /**
                 * IO任务
                 */
                //selectedKeys不为null,说明在构造函数中调用openSelector()方法进行初始化selector时,使用的是进行优化后的SelectKeySet
                //默认情况下都是使用的是优化后的SelectKeySet,所这里可以先着重看processSelectedKeysOptimized()方法的实现
                if (selectedKeys != null) {
                    processSelectedKeysOptimized(selectedKeys.flip());
                } else {
                    processSelectedKeysPlain(selector.selectedKeys());
                }
                //用系统当前时间-执行IO的开始时间 = 当前线程执行IO的总耗时
                final long ioTime = System.nanoTime() - ioStartTime;


                /**
                 * TODO:think about of:这里任务比例的计算方式
                 * 表示线程在执行任务过程中,IO任务所占的比例,以百分比为单位的,即:
                 *
                 */
                final int ioRatio = this.ioRatio;

                /**
                 * 非IO任务
                 */
                //运行任务队列中的其他任务
                runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                //判断当前evnetloop状态是否正在shuttingDown
                if (isShuttingDown()) {
                    closeAll();
                    if (confirmShutdown()) {
                        break;
                    }
                }
            } catch (Throwable t) {
                logger.warn("Unexpected exception in the selector loop.", t);

                // Prevent possible consecutive immediate failures that lead to
                // excessive CPU consumption.
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // Ignore.
                }
            }
        }
    }

run方法死循环,分别调用io跟非io任务,循环一开始先判断hasTasks()即阻塞队列中有无未执行任务,有的话SelectNow(),否则select()去轮询channel去取得io信息,selectNow()与select()的区别在于selectNow()没有tiemout,即使channel没有已经就绪的信息也会立即返回。若task队列空了则调用select()。

   //todo:通过封装后的select方法
    private void select() throws IOException {
        Selector selector = this.selector;
        try {
            int selectCnt = 0;
            long currentTimeNanos = System.nanoTime();
            long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
            for (; ; ) {
                long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
                if (timeoutMillis <= 0) {
                    if (selectCnt == 0) {
                        selector.selectNow();
                        selectCnt = 1;
                    }
                    break;
                }

                //此处select超时后,不会抛出异常
                int selectedKeys = selector.select(timeoutMillis);
                selectCnt++;

                if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks()) {
                    // Selected something,
                    // waken up by user, or
                    // the task queue has a pending task.
                    break;
                }

                if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
                        selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
                    // The selector returned prematurely many times in a row.
                    // Rebuild the selector to work around the problem.
                    logger.warn(
                            "Selector.select() returned prematurely {} times in a row; rebuilding selector.",
                            selectCnt);

                    rebuildSelector();
                    selector = this.selector;

                    // Select again to populate selectedKeys.
                    selector.selectNow();
                    selectCnt = 1;
                    break;
                }

                currentTimeNanos = System.nanoTime();
            }

            if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Selector.select() returned prematurely {} times in a row.", selectCnt - 1);
                }
            }
        } catch (CancelledKeyException e) {
            if (logger.isDebugEnabled()) {
                logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector - JDK bug?", e);
            }
            // Harmless exception - log anyway
        }
    }

有timeout时间去轮询,直到取到任务队列选定的东西,用户唤醒,或者有一个新的定时任务时,退出select()。

我们继续看run方法。

先着重看processSelectedKeysOptimized()方法的实现

private void processSelectedKeysOptimized(SelectionKey[] selectedKeys) {
        for (int i = 0; ; i++) {
            final SelectionKey k = selectedKeys[i];
            if (k == null) {
                break;
            }

            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) {
                selectAgain();
                // Need to flip the optimized selectedKeys to get the right reference to the array
                // and reset the index to -1 which will then set to 0 on the for loop
                // to start over again.
                //
                // See https://github.com/netty/netty/issues/1523
                selectedKeys = this.selectedKeys.flip();
                i = -1;
            }
        }
    }

遍历接收到的selectKey,再得到channel调用processSelectedKey(k, (AbstractNioChannel) a);如果还需要select调用selectAgain。初始化keys跟i。

processSelectedKey是真正处理selector当有io事件,产生的selectionKey

private static void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
        final NioUnsafe unsafe = ch.unsafe();
        if (!k.isValid()) {
            // close the channel if the key is not valid anymore
            unsafe.close(unsafe.voidPromise());
            return;
        }

        try {
            int readyOps = k.readyOps();
            // 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();
                if (!ch.isOpen()) {
                    // Connection already closed - no need to handle write.
                    return;
                }
            }
            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();
            }
            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();
            }
        } catch (CancelledKeyException e) {
            unsafe.close(unsafe.voidPromise());
        }
    }

通过selectKey的ops来判断io的类型,根据类型调用usafe的具体底层的io操作。

以usafe.read为例,usafe的一个实现类NioByteUnsafe的read操作

@Override
        public void read() {

            //得到配置信息
            final ChannelConfig config = config();
            final ChannelPipeline pipeline = pipeline();
            final ByteBufAllocator allocator = config.getAllocator();
            final int maxMessagesPerRead = config.getMaxMessagesPerRead();
            RecvByteBufAllocator.Handle allocHandle = this.allocHandle;
            if (allocHandle == null) {
                this.allocHandle = allocHandle = config.getRecvByteBufAllocator().newHandle();
            }
            if (!config.isAutoRead()) {
                removeReadOp();
            }

            ByteBuf byteBuf = null;
            int messages = 0;
            boolean close = false;
            try {
                int byteBufCapacity = allocHandle.guess();
                int totalReadAmount = 0;
                do {
                    byteBuf = allocator.ioBuffer(byteBufCapacity);
                    int writable = byteBuf.writableBytes();
                    int localReadAmount = doReadBytes(byteBuf);
                    if (localReadAmount <= 0) {
                        // not was read release the buffer
                        byteBuf.release();
                        close = localReadAmount < 0;
                        break;
                    }

                    //触发fireChannelRead事件
                    pipeline.fireChannelRead(byteBuf);
                    byteBuf = null;

                    if (totalReadAmount >= Integer.MAX_VALUE - localReadAmount) {
                        // Avoid overflow.
                        totalReadAmount = Integer.MAX_VALUE;
                        break;
                    }

                    totalReadAmount += localReadAmount;
                    if (localReadAmount < writable) {
                        // Read less than what the buffer can hold,
                        // which might mean we drained the recv buffer completely.
                        break;
                    }
                } while (++ messages < maxMessagesPerRead);

                pipeline.fireChannelReadComplete();
                allocHandle.record(totalReadAmount);

                if (close) {
                    closeOnRead(pipeline);
                    close = false;
                }
            } catch (Throwable t) {
                handleReadException(pipeline, byteBuf, t, close);
            }
        }
    }

封装调用了通过pipline编写的自己业务逻辑的部分,eventloop对io的操作到这里基本上很清晰了。

在完成io后,会计算ioRatio参数,表示了执行io时间与task时间的百分比。如果配置了100,那么执行队列中的任务会直到处理完信息之后开始,并直到处理完队列中的task之后才会继续尝试去取得select key,如果不是100,那么将会给执行队伍中task任务的时间设为执行io数据时间的(100- ioRatio)/ioRatio百分比的timeout。

最后就执行非io操作,通过runAllTasks()方法开始处理阻塞队列中的任务

 protected boolean runAllTasks(long timeoutNanos) {
        this.fetchFromDelayedQueue();
        Runnable task = this.pollTask();
        if(task == null) {
            return false;
        } else {
            long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;
            long runTasks = 0L;

            long lastExecutionTime;
            while(true) {
                try {
                    task.run();
                } catch (Throwable var11) {
                    logger.warn("A task raised an exception.", var11);
                }

                ++runTasks;
                if((runTasks & 63L) == 0L) {
                    lastExecutionTime = ScheduledFutureTask.nanoTime();
                    if(lastExecutionTime >= deadline) {
                        break;
                    }
                }

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

            this.lastExecutionTime = lastExecutionTime;
            return true;
        }
    }

首先通过fetchFromDelayedQueue将延时队列中超过diedline的定时任务放入阻塞队列中,准备开始执行。

最后在timeout时间内,不断执行task任务,时间则到退出。

猜你喜欢

转载自blog.csdn.net/panxj856856/article/details/80322209