netty EventLoop与Thread

线程模型

传统的线程池模型:jDK1.5之后的Executor的API,表现入下:

netty-7

  • 一个线程从池子中的free列表选中,并且指派一个提交的任务(实现了Runnable接口的任务)
  • 当任务完成的时候,这个线程将返回到list中,并且变得可用

总结

传统的这种池化以及可重用提升了线程的创建于销毁,但是并没有排除线程上下文的切换

EventLoop 接口

当连接发生的整个生命周期,运行一个任务处理事件,是一个基础的网络框架的功能。相应的架构通常是作为一个事件循环,netty是使用EventLoop这个接口实现的,如下图:
netty-7

一个EventLoop确定绑定在一个Thread上并且不会改变,一个EventLoop会被分配多个Channels
netty-7

通常Events以及任务是执行在一个FIFO序列中,这个排除数据出错的可能性,并且保证任务处理的正确顺序

I/O 以及事件处理在netty4中

所有的I/O操作以及事件处理都通过线程被指派在EventLoop上

I/O 以及事件处理在netty3中

所有的入站都由EventLoop执行,但是出站的操作是由调用它的线程处理,这个导致了一个问题,可能会导致异步操作,例如同时发生了两个出站的写操作,在不同的线程

另一个消极的方面是当一个入站事件产生了出站事件,但是出站的时候发生了异常,在netty3中,你需要调用线程来处理,增加了线程切换

在netty4中,解决了这个问题,提供了简单的执行架构,并且排除了在ChannelHandler上进行同步操作

任务调度

偶尔你需要延迟(推迟)执行一个任务,或者周期性的执行,你可能需要注册一个任务例如5分钟连接一次。一个通用的用例是心跳消息对远端执行。如果没有响应,你知道将要关闭这个channel

JDK的任务调度

在JDk1.5之前,通常使用java.util.Timer,随后JDk1.5之后使用java.util.concurrent,定义了一个ScheduledExecutorService,
netty-7

1
2
3
4
5
6
7
8
9
10
11
12
 ScheduledExecutorService executor =
Executors.newScheduledThreadPool(10);
ScheduledFuture<?> future = executor.schedule(
new Runnable() {
@Override
public void run() {
System.out.println("60 seconds later");

}
}, 60, TimeUnit.SECONDS);
......
executor.shutdown();

netty的任务调度使用的是EventLoop

1
2
3
4
5
6
7
8
Channel ch = ...
ScheduledFuture<?> future = ch.eventLoop().schedule(
new Runnable() {
@Override
public void run() {
System.out.println("60 seconds later");
}
}, 60, TimeUnit.SECONDS);
1
2
3
4
5
6
7
8
9
Channel ch = ...
ScheduledFuture<?> future = ch.eventLoop().scheduleAtFixedRate(
new Runnable() {
@Override
public void run() {
System.out.println("Run every 60 seconds");
}

}, 60, 60, TimeUnit.Seconds);

实现细节

这一节主要讲解重要元素线程模型以及调度实现的细节

线程管理

netty-7

事件循环与线程的分配

异步实现仅使用一点EventLoops,在单签的线程模型中,可能持有多个Channels,这个允许多个Channels可以用一个很小数量的线程,而不是每个线程都有一个channel

如下:

netty-7

一旦一个Channel已经被分配到了一个EventLoop,它将会在整个生命周期中使用EventLooop响应的线程

一个EventLoop需要使用ThreadLocal来保证线程安全,因为一个EvnetLoop通常持有多各Channel

OIO
netty-7

坚持原创技术分享,您的支持将鼓励我继续创作!