Netty源码解析之EventLoopGroup

背景参考

线程之ExecutorService

Reactor

  • 首先,搞懂JDK线程池
  • 再熟练掌握reactor模式
  • 最后再来理解Netty的线程模型

Future扩展

Future

  • 继承JDK的Future,提供更多状态方法,额外引入事件监听
  • 监听在操作完成后自动触发
  • 异步获取执行结果

Promise

  • 可写的Future
  • Future实际上是等待任务执行结束后写入结果,然后唤醒等待线程;Promise提供写方法,直接可以写入结果,唤醒等待线程。

Netty之EventExecutorGroup

概览

EventExecutorGroup和EventExecutor?

  • EventExecutor继承EventExecutorGroup,但是EventExecutorGroup的next()方法又返回EventExecutor对象。这里就陷入一个死循环了,先有鸡(EventExecutorGroup)还是先有蛋(EventExecutor)?这个地方其实是违反了依赖倒转设计原则的,父子接口互相依赖,因此这个地方很难理解。
public interface EventExecutorGroup extends ScheduledExecutorService, Iterable<EventExecutor> {
		//省略其他方法
  
    EventExecutor next();
    @Override
    Iterator<EventExecutor> iterator();
}
public interface EventExecutor extends EventExecutorGroup {
		//省略其他方法
  
    @Override
    EventExecutor next();
    EventExecutorGroup parent();
    boolean inEventLoop();
    boolean inEventLoop(Thread thread);
  • 我们在使用JDK线程池时,任务之间其实是独立的,我们并不关心他们的执行先后顺序,因为我们的任务都是原子的,因此我们并不关心任务被哪个线程拿到执行。所以,JDK的线程之并没有给我们提供获取内部线程的方法,我们也不需要。

  • 首先,我们从Netty的线程模型入手,分析这里的设计原因:在Netty中,我们的一个Channel是绑定到一个线程上的,也就是一个Channel绑定到一个线程上,一个线程可以绑定多个Channel。绑定的好处是一个Channel上的所有操作都是串行的,因为只有一个线程处理这个Channel。如果是直接把Channel的操作提交给线程池,那么可能读写等操作乱序,需要额外的机制保证并发,这种绑定避免了这些额外开销。

  • 对比JDK和Netty的线程池实现,总结如下:JDK线程池提交的任务是独立的,Netty提交的任务是需要保证执行顺序的。

  • 从第三点我们知道,Netty中,我们需要把Channel绑定到一个特定的线程上去,因此我们需要获取到线程池的某个线程,并且这个线程可以当成一个线程池来使用,我们可以向这个线程提交任务。EventExecutor也提供了inEventLoop方法用户判断当前代码执行是不是在绑定的线程,如果不是,我们就需要通过提交任务的方式提交,如果是,我们就可以直接执行,因此我们可以看到很多类似代码

 		//获取绑定的线程
		EventLoop eventLoop = eventLoop();
	      //如果当前线程是绑定的线程,直接执行
            if (eventLoop.inEventLoop()) {
                setReadPending0(readPending);
            //否则,提交到绑定线程中执行
            } else {
                eventLoop.execute(new Runnable() {
                    @Override
                    public void run() {
                        setReadPending0(readPending);
                    }
                });
            }

AbstractEventExecutorGroup

  • 同理,既然EventExecutor继承EventExecutorGroup,那么EventExecutorGroup其实也不需要具体实现业务逻辑,委托给EventExecutor具体实现即可,因此AbstractEventExecutorGroup中都是
    @Override
    public Future<?> submit(Runnable task) {
        return next().submit(task);
    }

MultithreadEventExecutorGroup

  • 维护一组EventExecutor,使用EventExecutorChooser进行选择
  • 子类负责创建具体的EventExecutor实现

DefaultEventExecutorGroup

  • 默认的事件执行器组
  • 创建DefaultEventExecutor作为具体任务执行器

DefaultEventExecutor

  • 维护一个线程,循环执行提交给它的任务

    @Override
    protected void run() {
        for (;;) {
            Runnable task = takeTask();
            if (task != null) {
                task.run();
                updateLastExecutionTime();
            }

            if (confirmShutdown()) {
                break;
            }
        }
    }

DEMO

    public static void main(String[] args) {
        EventExecutorGroup eventExecutorGroup = new DefaultEventExecutorGroup(5);
        eventExecutorGroup.execute(() -> System.out.println(Thread.currentThread().getName()));
        eventExecutorGroup.execute(() -> System.out.println(Thread.currentThread().getName()));
        eventExecutorGroup.execute(() -> System.out.println(Thread.currentThread().getName()));
        eventExecutorGroup.execute(() -> System.out.println(Thread.currentThread().getName()));
        EventExecutor eventExecutor = eventExecutorGroup.next();
        eventExecutor.execute(() -> System.out.println(Thread.currentThread().getName()));
        eventExecutor.execute(() -> System.out.println(Thread.currentThread().getName()));
        eventExecutor.execute(() -> System.out.println(Thread.currentThread().getName()));
        eventExecutor.execute(() -> System.out.println(Thread.currentThread().getName()));
    }
defaultEventExecutorGroup-2-1
defaultEventExecutorGroup-2-2
defaultEventExecutorGroup-2-3
defaultEventExecutorGroup-2-4
defaultEventExecutorGroup-2-5
defaultEventExecutorGroup-2-5
defaultEventExecutorGroup-2-5
defaultEventExecutorGroup-2-5
  • 前四次是提交给EventExecutorGroup的,因此给不同线程执行的
  • 后四次是提交给一个指定的eventExecutor的,所以由这个执行器所在线程执行,后面四个任务顺序执行
  • 需要执行顺序任务,就可以使用Netty提供的线程池,提交到同一个eventExecutor

总结

  • EventExecutorGroup就是Netty的线程池,对Future进行扩展,添加更丰富的操作,同时可以以监听的形式处理任务完成操作,避免无效get等待
  • EventExecutorGroup可以获取单个的EventExecutor,提交给单个EventExecutor的任务串行执行
  • 提交给EventExecutorGroup的任务轮询选择EventExecutor提交执行

Netty之EventLoopGroup

  • 同理EventLoopGroup和EventLoop的关系跟EventExecutorGroup和EventExecutor的关系是一样的
  • EventLoopGroup继承EventExecutorGroup,因此也提供了线程池功能
  • 提供了额外注册Channel的方法
public interface EventLoopGroup extends EventExecutorGroup {
   
    @Override
    EventLoop next();

    ChannelFuture register(Channel channel);

    ChannelFuture register(ChannelPromise promise);

    @Deprecated
    ChannelFuture register(Channel channel, ChannelPromise promise);
}
  • 从上面我们知道,Netty提供了一个Channel绑定到一个线程,一个线程可以绑定多个Channel,因此EventLoopGroup主要是实现这个概念的
  • 同样,EventLoopGroup是不干事的,它的实现就是把调用交给EventLoop完成

NioEventLoop

  • NIO常用的EventLoop实现
  • 从继承结构可以知道,这是一个线程池的单线程实现,它也是EventExecutor子类,所以包含前面提到的功能,同时,他额外提供了Channel注册的功能

Channel注册到EventLoop上有什么用呢?

  • EventExecutor本质是维护一个线程,然后run方法里面死循环执行任务
  • EventLoop继承EventExecutor,在run方法里进行额外操作
  • 以NioEventLoop为例,它包含一个成员变量Selector,这是NIO提供的,Channel注册实际上是注册到Selector上,在NioEventLoop的run方法里面,它的功能分为两部分:处理Selector的io事件、执行提交的任务。同时,我们还可以通过ioRatio字段控制两部分功能执行时间占比。
  • 也就是一个Channel注册到EventLoop后,它就注册到了EventLoop的Selector上了,EventLoop的线程就会对Selector的io事件进行处理了。

总结

EventLoopGroup bossGroup = new NioEventLoopGroup();
  • 上面代码实际创建了一个线程池
  • 这个线程池可以获取实际执行任务的每一个EventLoop
  • 每个EventLoop都维护一个线程,可以不断串行执行提交的任务
  • NioEventLoopGroup维护了一个Selector变量,绑定channel实际上就是把NIO的Channel注册到Selector上
  • EventLoop的run方法负责两部分功能:1)处理Selector上的io时间,2)处理提交给它的任务
public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            new ServerBootstrap().group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new FixedLengthFrameDecoder(3))
                    .localAddress(8888)
                    .bind()
                    .sync()
                    .channel()
                    .closeFuture()
                    .sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
  • 上面这个代码,其实就是创建了两个线程池EventLoopGroup,指定了底层通道实现NioServerSocketChannel
  • NioServerSocketChannel实例化对象后,会注册到bossGroup上;新开启的NioSocketChannel会注册到

workerGroup上,这两个线程池对Selector的io操作进行处理

posted @ 2020-06-29 10:44  java拌饭  阅读(673)  评论(0编辑  收藏  举报