Netty系列:七、EventLoop的线程模型

1.线程池概述

线程模型确定了代码的执行方式。Java 5 随后引入了ExecutorAPI,其线程池通过缓存和重用Thread 极大地提高了性能。

  • 线程池基础 >>线程池原理
    • 从池的空闲线程列表中选择一个 Thread,并且指派它去运行一个已提交的任务(一个Runnable 的实现);
    • 当任务完成时,将该 Thread 返回给该列表,使其可被重用。

虽然池化和重用线程相对于简单地为每个任务都创建和销毁线程是一种进步,但是它并不能消除由上下文切换所带来的开销,其将随着线程数量的增加很快变得明显,并且在高负载下愈演愈烈。此外,仅仅由于应用程序的整体复杂性或者并发需求,在项目的生命周期内也可能会出现其他和线程相关的问题。

2.EventLoop接口

运行任务来处理在连接的生命周期内发生的事件是任何网络框架的基本功能。与之相应的编程上的构造通常被称为事件循环——一个 Netty 使用了interface io.netty.channel.EventLoop来适配的术语。

事件循环的基本思想,其中每个任务都是一个Runnable的实例:

while (!terminated) {
    List<Runnable> readyEvents = blockUntilEventsReady();
    for (Runnable ev: readyEvents) {
        ev.run();
    }
}

Netty的EventLoop是协同设计的一部分,它采用了两个基本的 API:并发和网络编程。首先,io.netty.util.concurrent包构建在 JDK 的java.util.concurrent包上,用来提供线程执行器。其次,io.netty.channel包中的类,为了与Channel的事件进行交互,扩展了这些接口/类。

在这个模型中,一个EventLoop将由一个永远都不会改变的Thread驱动,同时任务(Runnable 或者 Callable)可以直接提交给EventLoop实现,以立即执行或者调度执行。根据配置和可用核心的不同,可能会创建多个EventLoop实例用以优化资源的使用,并且单个EventLoop可能会被指派用于服务多个Channel。Netty的EventLoop只定义了一个方法,parent()。这个方法,用于返回到当前EventLoop实现的实例所属的EventLoopGroup的引用。

事件处理
由 I/O 操作触发的事件将流经安装了一个或者多个ChannelHandlerChannelPipeline。传播这些事件的方法调用可以随后被ChannelHandler所拦截,并且可以按需地处理事件。在Netty 4 中,所有的I/O操作和事件都由已经被分配给了EventLoop的那个Thread来处理。

3.任务调度

3.1.JDK的任务调度

JDK 提供了java.util.concurrent包,它定义了
interface ScheduledExecutorService接口,可以通过Executors的静态方法生成它的实例。有兴趣的请看 >>线程池相关

ScheduledExecutorService executor =
Executors.newScheduledThreadPool(10);
ScheduledFuture<?> future = executor.schedule(
    new Runnable() {
    @Override
        public void run() {
        System.out.println("60 seconds later");
        }
    //调度任务在从现在开始
的 60 秒之后执行
    }, 60, TimeUnit.SECONDS);
...
executor.shutdown();

虽然 ScheduledExecutorService API是直截了当的,但是在高负载下它将带来性能上的负担

3.2使用EventLoop调度任务

ScheduledExecutorService的实现具有局限性,例如,事实上作为线程池管理的一部分,将会有额外的线程创建。如果有大量任务被紧凑地调度,那么这将成为一个瓶颈。Netty 通过 Channel 的EventLoop实现任务调度解决了这一问题

Channel ch = ...
ScheduledFuture<?> future = ch.eventLoop().schedule(
    new Runnable() {
    @Override
    public void run() {
        System.out.println("60 seconds later");
    }
}, 60, TimeUnit.SECONDS);

经过 60 秒之后,Runnable 实例将由分配给 Channel 的EventLoop 执行。

如果当前调用线程正是支撑 EventLoop 的线程,那么所提交的代码块将会被(直接)执行。否则,EventLoop 将调度该任务以便稍后执行,并将它放入到内部队列中。当 EventLoop下次处理它的事件时,它会执行队列中的那些任务/事件。每个 EventLoop 都有它自已的任务队列,独立于任何其他的 EventLoop。
netty

4. EventloopGroup

EventLoopGroup 为 Netty 的 Reactor 线程池,它实际上就是 EventLoop 的容器,而 EventLoop 为 Netty 的核心抽象类,它的主要职责是处理所有注册到本线程多路复用器 Selector 上的 Channel。我们知道 Eventloop 包含在 EventloopGroup 中,根据不同的传输实现,EventLoop 的创建和分配方式也不同。.

  • 一个 EventLoopGroup 包含一个或多个 EventLoop。
  • 一个 EventLoop 在它的生命周期内只能与一个Thread绑定。
  • 所有有 EnventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理。
  • 一个 Channel 在它的生命周期内只能注册与一个 EventLoop。

当一个连接到达时,Netty 就会注册一个 Channel,然后从 EventLoopGroup 中分配一个 EventLoop 绑定到这个Channel上,在该Channel的整个生命周期中都是有这个绑定的 EventLoop 来服务的。

4.1.异步传输

异步传输实现只使用了少量的 EventLoop(以及和它们相关联的 Thread),而且在当前的线程模型中,它们可能会被多个 Channel 所共享。这使得可以通过尽可能少量的 Thread 来支撑大量的 Channel,而不是每个 Channel 分配一个 Thread。

netty
一个 EventLoopGroup,它具有 3 个固定大小的 EventLoop(每个 EventLoop都由一个 Thread 支撑)。在创建 EventLoopGroup 时就直接分配了 EventLoop(以及支撑它们的 Thread),以确保在需要时它们是可用的。

4.2.阻塞传输

netty
这种情况下保证是每个 Channel 的 I/O 事件都将只会被一个 Thread(用于支撑该 Channel 的 EventLoop 的那个 Thread)处理。

猜你喜欢

转载自blog.csdn.net/TheLudlows/article/details/79570464