netty5笔记-线程模型2-EventLoopGroup

     阅读本文之前,你需要对java的线程池有一定的了解,因为这里不会过多的讲解。

    今天我们主要的任务就是看下netty中一个非常重要的类EventLoop,通过这系列文章,你应该了解EventLoop适用的场景,不会滥用它而导致你的应用缓慢。Netty使用了典型的Reactor模型结构,这其中一个很重要的角色就是EventLoop,它使用循环的方式来处理IO或者其他事件。


        上图是EventLoop的接口继承关系,其中Executor、ExecutorService、ScheduledExecutorService是java提供的线程池管理接口:

ScheduledExecutorService:提供执行计划任务的接口;

EventExecutorGroup:提供管理EventExecutor的能力,他通过next()来为任务分配执行线程,同时也提供了shutdownGracefully这一优雅下线的接口;

方法 说明
shutdownGraceFully 优雅关闭(何为优化关闭后面会介绍)
isShuttingDown 其管理的所有EventExecutor是否关闭
terminationFuture 返回接收该线程池彻底关闭事件的Future
children 含所有管理的EventExecutor
next 通过该方法来为任务分配一个EventExecutor
EventExecutor:实际的事件执行者
方法 说明
parent 管理它的EventExecutorGroup
isEventLoop 当前线程与EventExecutor的执行线程是否是同一个线程,如果是则此处返回true
newPromise 创建一个Promise,由该EventExecutor来执行Promise中的listener
EventLoopGroup: EventLoopGroup和EventLoop的关系与EventExecutorGroup和EventExecutor的关系类似

方法 说明
register 将channel注册到该EventLoopGroup,注册后EventLoop会负责该channel的相关io事件
EventLoop:处理所有的IO操作。EventLoop继承了EventLoopGroup接口,可以被当做一个single的线程池看到(虽然模式差不多,但其实和java的single线程池区别很大)。

       我们找一个最常用的EventLoop实现类来介绍:NioEventLoop。介绍它之前我们得先介绍NioEventLoopGroup,一个连接被它分配到对应的NioEventLoop并进行一系列的后续操作。先看看NioEventLoopGroup的构造函数,最终调用的是下面这个构造方法:

       private MultithreadEventExecutorGroup(int nEventExecutors,
                                          Executor executor,
                                          boolean shutdownExecutor,
                                          Object... args) {
       if (nEventExecutors <= 0) {
            throw new IllegalArgumentException(
                    String.format("nEventExecutors: %d (expected: > 0)", nEventExecutors));
        }

        if (executor == null) {
            executor = newDefaultExecutorService(nEventExecutors);
            shutdownExecutor = true;
        }

        // 根据nEventExecutors确定EventExecutor的数量
        children = new EventExecutor[nEventExecutors];
        // 用了两种不同的方式来为一个任务分配EventExecutor,。
        // 两种实现结果是相同的,但是第一种利用的位运算,相对效率更高点。。。
        // 具体的实现是从children的第一个开始获取,从0->size-1依次取child,到达最后一个后回到第一个child,最终形成一个环形数组。
        if (isPowerOfTwo(children.length)) {
            chooser = new PowerOfTwoEventExecutorChooser();
        } else {
            chooser = new GenericEventExecutorChooser();
        }

        // 开始初始化每个EventExecutor
        for (int i = 0; i < nEventExecutors; i ++) {
            boolean success = false;
            try {
                // 实际的初始化由子类自己实现,如NioEventLoopGroup的实现为:
                // return new NioEventLoop(this, executor, (SelectorProvider) args[0]);
                children[i] = newChild(executor, args);
                success = true;
            } catch (Exception e) {
                // TODO: Think about if this is a good exception type
                throw new IllegalStateException("failed to create a child event loop", e);
            } finally {
                // 如果初始化的过程中发生异常,则将初始化好的EventExecutor全部关闭
                if (!success) {
                    for (int j = 0; j < i; j ++) {
                        children[j].shutdownGracefully();
                    }

                    for (int j = 0; j < i; j ++) {
                        EventExecutor e = children[j];
                        // 等待关闭完成
                        try {
                            while (!e.isTerminated()) {
                                e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
                            }
                        } catch (InterruptedException interrupted) {
                            // Let the caller handle the interruption.
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                }
            }
        }

        final boolean shutdownExecutor0 = shutdownExecutor;
        final Executor executor0 = executor;
        final FutureListener<Object> terminationListener = new FutureListener<Object>() {
            @Override
            public void operationComplete(Future<Object> future) throws Exception {
                // 最后一个关闭完成则标记future完成
                if (terminatedChildren.incrementAndGet() == children.length) {
                    terminationFuture.setSuccess(null);
                    if (shutdownExecutor0) {
                        // This cast is correct because shutdownExecutor0 is only try if
                        // executor0 is of type ExecutorService.
                        ((ExecutorService) executor0).shutdown();
                    }
                }
            }
        };

        // 下面的代码比较简单,不过多介绍
        xxxxxxxxxxxxxxxxxxxxxxxx
    }

    // 看看这两个实现类的差异,这效率扣得不要不要的啊!
    private final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
        @Override
        public EventExecutor next() {
            return children[childIndex.getAndIncrement() & children.length - 1];
        }
    }

    private final class GenericEventExecutorChooser implements EventExecutorChooser {
        @Override
        public EventExecutor next() {
            return children[Math.abs(childIndex.getAndIncrement() % children.length)];
        }
    }
        我们在使用NioEventGroupLoop的时候,一般都是直接使用默认构造方法,此时第一个参数nEventExecutors=cpu核数 x 2。NioEventGroupLoop中有很大部分的io操作,这个默认值比较靠谱,不用用户再去修改。

    private static final int DEFAULT_EVENT_LOOP_THREADS;

    static {
        DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                "io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));

        if (logger.isDebugEnabled()) {
            logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
        }
    }
       构造方法的第二个参数executor,它是执行EventExecutor中的任务的实际线程池。默认使用的是netty实现的ForkJoinPool(比较复杂,有空再回过头来分析)。可以看出NioEventLoop本身是不负责线程的创建销毁的,他把执行逻辑封装在Runnable中交给executor处理,这里的模型和netty4已经不太一样,4的EventLoop对应一个固定线程,而5的EventLoop并未固定到一个线程。这也是我困惑的地方,executor的线程数与EventLoop个数相同,能保证每个EventLoop都有线程去执行,但是每个EventLoop不再是固定的Thread了,它带来的问题是一些ThreadLocal的cache可能会失效。不知道为何会这样设计,先在这留个疑问吧,等release版本出来了再看看。

       构造方法的最后一个参数args[0]=SelectorProvider.provider(); SelectorProvider根据不同的操作系统创建出对应的provider,如linux下创建的sun.nio.ch.EPollSelectorProvider该参数在NioEventLoop初始化的时候被传入,用于创建Selector(这里有一篇Selector的介绍)。

   往EventLoopGroup中提交一个任务,实际上就是交给其child(即EventLoop)处理

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值