线程模型概述

在本节中,我们将介绍线程模型,然后讨论Netty过去和现在的线程模型,审查每个模型的优点和局限性。
线程模型指定了代码被执行的方式。因为我们一定要防范并发执行可能的副作用,所以了解使用的模型会产生的影响很重要(也有单线程模型)。

因为具有多核或CPU的计算机是常见的,有很多应用程序采用复杂的多线程技术来有效利用
系统资源。相比之下,我们在Java早期的应用多线程方法仅仅是创建和启动新的线程,以执行并发工作单元,这是一种在高负载下工作不良的原始方法。 Java 5然后引入了了Executor API,其线程池通过线程缓存和重用大大提高了性能。

基本线程池模型可以描述为:

  • 从线程池的空闲线程列表中选出一个线程来执行一个提交的任务
  • 任务完成后,线程回到线程池并且可以被复用

如下图所示:


缓存和重用线程是创建和销毁线程的一个改进,但它不消除上下文切换的成本,随着线程数量的增加而变得明显,并且可能会造成高负载。 此外,在项目的生命周期中可能会出现其他线程相关的问题,只是因为应用程序的整体复杂性或并发性要求。

简之,多线程编程可能很复杂。
在接下来的部分我们将看看Netty是怎样简化它的

EventLoop接口

网络编程框架的基本功能是,运行任务(线程)来处理在连接期间发生的事件。相应的编程结构经常被称作一个事件循环。Netty通过接口io.netty.channel.EventLoop实现了这个结构。

一个事件循环的基本概念如下代码所示:

while (!terminated) {//阻塞直到有准备好的事件List<Runnable> readyEvents = blockUntilEventsReady();for (Runnable ev: readyEvents) {ev.run();}
}

Netty的EventLoop是协同设计(collaborative design)的一部分,它应用了两种基本的API:并发和网络编程。首先io.netty.util.concurrent包构建在JDKjava.util.concurrent之上来提供线程executors。然后,在io.netty.channel包中的与EventLoop相关的类继承了java.util.concurrent中的(Executor)类来与Channel的事件交互。类结构图如下图所示:

在这个模型中,EventLoop由一个线程驱动(这种关系是稳定的,不会切换到其他线程),
并且任务(Runnable或Callable)可以直接提交给EventLoop的实现,以便立即执行或计划执行。 取决于配置和可用的CPU数,可以创建多个EventLoop以优化资源使用,并且可以通过单个EventLoop来服务多个Channel。

注意到Netty的EventLoop继承了ScheduledExecutorService,并且EventLoop只定义了一个parent()方法。这个方法,如下面的代码片段所示,是用来返回当前EventLoop(实现类的)实例所属的EventLoopGroup的一个引用。

public interface EventLoop extends EventExecutor, EventLoopGroup {@OverrideEventLoopGroup parent();
}

事件/任务执行顺序 事件和任务以先进先出的顺序执行。通过保证以正确的顺序处理字节内容来消除数据损坏的可能性。

Netty4中IO和事件的处理

IO操作触发的事件流过具有一个或多个ChannelHandler的ChannelPipeline。这些事件能被ChannelHandler拦截并且在需要的时候对事件进行处理。事件的性质通常决定如何处理,这些特性可能会使数据从网络协议栈传播到你的应用,或者相反。但事件处理逻辑必须具有通用性和灵活性使得足够处理所有可能的情况。

因此,在Netty4中,所有的IO操作和事件都由已分配给EventLoop的线程处理。

任务调度

你偶尔需要安排一个任务延后执行或周期性的执行。例如,你想注册一个任务来监听客户端已经连上5分钟了,常见的做法是发送一个心跳信息到远端来检测它是否是连接状态,如果远端没有响应,你就能关闭channel了。
下一节,你会看到如何通过Java API和Netty的EventLoop来进行任务调度。

JDK调度API

Java5以前的调度是基于java.util.Timer类的,它利用了一个后台线程同时具有与标准线程一样的局限。因此,JDK提供java.uitl.concurrent包定义了ScheduledExecutorService接口。

下面的代码显示了如何通过ScheduledExecutorService在60秒延迟后执行一个任务:

ScheduledExecutorService executor =Executors.newScheduledThreadPool(10);
ScheduledFuture<?> future = executor.schedule(new Runnable() {@Overridepublic void run() {System.out.println("60 seconds later");}}, 60, TimeUnit.SECONDS);
...
executor.shutdown();

虽然ScheduledExecutorService的API很简单,但在高负载情况下会导致开销很大。

使用EventLoop来调度任务

ScheduledExecutorService的实现有它的限制,如创建额外的线程是线程池管理的一部分,这可能会成为瓶颈。比如同时安排很多任务时。Netty通过使用Channel的EventLoop实现的调度来解决这个问题。如下面的代码所示:

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

60秒之后,channel的EventLoop会执行这个Runnable实例。
如果想每60秒执行一个任务,可以通过scheduleAtFixedRate(),如下所示:

Channel ch = ...
ScheduledFuture<?> future = ch.eventLoop().scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {System.out.println("Run every 60 seconds");}}, 60, 60, TimeUnit.Seconds);//首先60秒后执行,然后每60秒执行一次

这是我测试一下当任务本身的时间消耗超过这个时间间隔会怎样:

 ch.eventLoop().scheduleAtFixedRate(new Runnable() {public void run() {System.out.println(new Date().toLocaleString()+" : do it...");try {TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}},1,5,TimeUnit.SECONDS);

这里的时间间隔是5秒钟,但是任务需要执行10秒,那么输出会怎样:

2017-9-8 17:52:12 : do it...
2017-9-8 17:52:22 : do it...
2017-9-8 17:52:32 : do it...
2017-9-8 17:52:42 : do it...

可看到,如果任务需要执行时间超过时间间隔,那么间隔会变成任务的执行时间。

可以通过scheduleAtFixedRate()返回的ScheduledFuture来取消执行或查看一个执行的状态。下面的代码显示了一个简单的取消操作:

ScheduledFuture<?> future = ch.eventLoop().scheduleAtFixedRate(...);
// Some other code that runs...
boolean mayInterruptIfRunning = false;
future.cancel(mayInterruptIfRunning);//取消这个任务使它不再执行

这些例子说明了利用Netty的调度功能可以实现的性能提升。这些依赖于底层的线程模。

实现细节

线程管理

Netty的线程模型的优越性能取决于确定当前正在执行的线程的身份 也就是说,它是否是被分配
到当前Channel及其EventLoop。 (回想一下EventLoop是负责用于处理Channel生命周期中的所有事件)

如果调用Thread是EventLoop,则执行该代码块。 否则,EventLoop会调度一个任务用来延迟执行,并将其放在内部队列。当EventLoop再一次处理它的事件时,它将执行队列中的那些任务。 这解释了任何线程在不需要ChannelHandler中的同步的情况下如何直接与Channel交互。

每个EventLoop都有它自己的任务队列。上图显示了EventLoop调度任务时的执行逻辑。
我们之前声明了不阻塞当前IO线程的重要性。永远不要将一个耗时很长的任务放到执行队列,因为它会阻塞其他要在同一个线程上执行的任务。如果你必须进行阻塞调用或执行耗时很长的任务,我们建议使用一个专用的EventExecutor(DefaultEventExecutorGroup)。

EventLoop/线程分配

EventLoopGroup中包含服务IO和通channel件的EventLoops。EventLoops创建和分配的方式根据传输服务实现而有所不同。

异步的传输服务

异步实现只使用几个EventLoops(及其关联的线程),并且在当前模型中,这些可以在通道之间共享。这允许
许多Channel由尽可能少的线程服务,而不是为每个channel分配一个线程。

上图显示了一个固定大小为三个EventLoops(每个分配了一个线程)的EventLoopGroup。EventLoop(和它们的线程)被直接分配来确保它们在需要时可用。EventLoopGroup负责将EventLoop分配给每个新创建的Channel。在当前的实现中,使用循环方式实现平衡分配,并且可以将相同的EventLoop分配给多个Channel(这可能会在将来的版本中更改)。

一旦一个Channel被分配到一个EventLoop,它的整个生命周期内都会使用这个EventLoop。同样也要注意EventLoop分配实现中用到了ThreadLocal。因为一个EventLoop通常不仅为一个Channel服务,所有相关的Channel都能看到同一个ThreadLocal。这不利于实现像状态跟踪功能。然后,在无状态的环境中,对于在Channel之间分享巨大或开销大的对象甚至是事件都是很有用的。

阻塞的传输服务

其他传输服务(比如OIO)的设计和上面的有点不同,如下图所示:

每个EventLoop只分配给一个Channel。它保证了每个Channel的IO事件只会被一个线程处理。这是Netty一致性设计的另一个例子,它对Netty的可靠性和易用性贡献巨大。

Netty in action—EventLoop和线程模型相关推荐

  1. 【Netty】EventLoop和线程模型

    一.前言 在学习了ChannelHandler和ChannelPipeline的有关细节后,接着学习Netty的EventLoop和线程模型. 二.EventLoop和线程模型 2.1. 线程模型 线 ...

  2. Netty实战七之EventLoop和线程模型

    简单地说,线程模型指定了操作系统.编程语言.框架或者应用程序的上下文中的线程管理的关键方面.Netty的线程模型强大但又易用,并且和Netty的一贯宗旨一样,旨在简化你的应用程序代码,同时最大限度地提 ...

  3. netty的使用场景,线程模型以及如何在springboot中使用netty?

    文章目录 1. 为什么使用netty? 2. netty的线程模型 3. 在springboot中使用netty 4. netty的核心API解释 5. netty中的ByteBuf 1. 为什么使用 ...

  4. Netty和RPC框架线程模型分析

    <Netty 进阶之路>.<分布式服务框架原理与实践>作者李林锋深入剖析Netty和RPC框架线程模型.李林锋已在 InfoQ 上开设 Netty 专题持续出稿,感兴趣的同学可 ...

  5. Netty 和 RPC 框架线程模型分析

    https://www.infoq.cn/article/9Ib3hbKSgQaALj02-90y 1. 背景 1.1 线程模型的重要性 对于 RPC 框架而言,影响其性能指标的主要有三个要素: I/ ...

  6. 一道Netty面试题:boss线程池和worker线程池能不能合在一起?

    前言 这篇帖子我估计要反复修改,我不确定面试官是不是随口问的(就是可能他自己也没仔细想过这个问题...),我当时回答的是不能,我确实不大明白为啥要合在一起,合在一起你也是要有线程去处理连接,一部分线程 ...

  7. Netty Reactor线程模型与EventLoop详解

    本文来说下Netty Reactor线程模型与EventLoop 文章目录 EventLoop事件循环 任务调度 线程管理 线程分配 非阻塞传输 阻塞传输 Netty线程模型 单Reactor单线程模 ...

  8. Netty系列之Netty线程模型

    关注点在于:如何灵活的动态绑定IO事件处理,又能进行串行化处理减少锁的使用 摘自:http://www.infoq.com/cn/articles/netty-threading-model 1. 背 ...

  9. netty reactor线程模型分析

    netty4线程模型 ServerBootstrap http示例 // Configure the server.EventLoopGroup bossGroup = new EpollEventL ...

  10. Reactor三种线程模型与Netty线程模型

    一.Reactor三种线程模型 1.1.单线程模型 单个线程以非阻塞IO或事件IO处理所有IO事件,包括连接.读.写.异常.关闭等等.单线程Reactor模型基于同步事件分离器来分发事件,这个同步事件 ...

最新文章

  1. android ajax 跨域更新本地html,本地webapp是怎么解决跨域问题的?
  2. angularjs 中的$digest和$apply区别
  3. 使用 Nginx 代理 Socket.io/WebSocket 及 负载均衡配置
  4. [笔记]画三角函数-涵盖画图基础
  5. Ticket 服务: 一种经济的分布式唯一主键生成方案
  6. 技术人的少年感,和年龄无关。
  7. 【Qt for Android】OpenGL ES 绘制彩色立方体
  8. 不可不知的站群外推方法与技巧
  9. “先粗后精”的实例分割,BPR:使用Crop-then-Refine的性能提高方法
  10. TSAP(4) : 时间序列采样[asfreq( ) VS resample( )]
  11. 自动控制原理8.3---相平面法
  12. 如何制作伪原创视频?呆头鹅批量视频剪辑软件一键处理10万个视频
  13. 零基础CSS入门教程(30)–CSS布局实例
  14. 模拟静态小米商城官网html+css
  15. python基础----Day07
  16. TypeError: only integer tensors of a single element can be converted to an index
  17. 【HTML】Canvas(3)-绘制图片
  18. 【342期】SpringBoot + Redis 布隆过滤器防恶意流量击穿缓存的正确姿势!
  19. 卡尔曼滤波Kalman Filtering:介绍
  20. DTMF通信系统设计—基于MATLAB和STM32

热门文章

  1. 如何辨别真假柯达胶卷
  2. java day10【接口、多态】
  3. SonarLint插件的安装与使用
  4. PMP杂谈--PMP中一些easy忽视的地方
  5. KnockoutJS(4)-控制文本和外观绑定
  6. mongodb的副本集总结
  7. 由scanf说起之1:scanf函数和回车、空格 及其返回值
  8. shift and算法
  9. [JZOJ100026]图--倍增
  10. 实践中 XunSearch(讯搜)更新索引方案对比