Netty学习摘记 —— 再谈EventLoop 和线程模型
本文参考
本篇文章是对《Netty In Action》一书第七章"EventLoop和线程模型"的学习摘记,主要内容为线程模型的概述、事件循环的概念和实现、任务调度和实现细节
线程模型概述
线程模型指定了操作系统、编程语言、框架或者应用程序的上下文中的线程管理的关键方面。可见,线程模型确定了代码的执行方式,如何以及何时创建线程将对应用程序代码的执行产生显著的影响,因此开发人员需要理解与权衡不同的模型
在早期的 Java 语言中,我们使用多线程处理的主要方式无非是按需创建和启动新的 Thread 来执行并发的任务单元 —— 一种在高负载下工作得很差的原始方式。Java 5 随后引入了 Executor API(Java 的并发 API —— java.util.concurrent),其线程池通过缓存和重用 Thread极大地提高了性能,基本的线程池化模式可以描述为:
从池的空闲线程列表中选择一个 Thread,并且指派它去运行一个已提交的任务(一个 Runnable的实现);
当任务完成时,将该Thread返回给该列表,使其可被重用
java.util.concurrent
public interface ExecutorAn object that executes submitted Runnable tasks. This interface provides a way of decoupling task submission from the mechanics of how each task will be run, including details of thread use, scheduling, etc. An Executor is normally used instead of explicitly creating threads.
虽然池化和重用线程相对于简单地为每个任务都创建和销毁线程是一种进步,但是它并不能消除由上下文切换所带来的开销,它的性能瓶颈随着线程数量的增加和负载的增大很快变得明显
EventLoop接口
运行任务来处理在连接的生命周期内发生的事件是任何网络框架的基本功能。与之相应的编程上的构造通常被称为事件循环 —— 一个 Netty 使用了 interface io.netty.channel. EventLoop来适配的术语
在前面的"深入理解Netty核心组件"的文章中,我们提供了下面这张图:
图中标注的④、⑤、⑥和⑦便体现了事件循环的基本思想,在Netty中使用了AtomicBoolean类型的变量wakenUp作为超时标记
Boolean that controls determines if a blocked Selector.select should break out of its selection process. In our case we use a timeout for the select method and the select method will block for that time unless waken up.
事件循环中执行任务的一个极简化的代码如下:
boolean terminated = true;
//...
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的事件进行交互, 扩展了这些接口/类,下图展示了类与接口的继承和实现关系
事件和任务是以先进先出(FIFO)的顺序执行。这样可以通过保证字节内容总是按正确的顺序被处理,消除潜在的数据损坏的可能性,例如在SingleThreadEventLoop中定义了Queue<Runnable>类型的tailTasks变量来添加需要执行的任务
注意每个 EventLoop 都有它自已的任务队列,独立于任何其他的 EventLoop。
Netty 4 中的 I/O 和事件处理
由 I/O 操作触发的事件将流经安装了一个或者多个 ChannelHandler的ChannelPipeline。传播这些事件的方法调用可以随后被ChannelHandler所拦截,并且可以按需地处理事件
并且所有的I/O操作和事件都由已经被分配给了 EventLoop的那个Thread来处理
而在以前的版本中所使用的线程模型,只保证了入站(之前称为上游)事件会在所谓的 I/O 线程(对应于 Netty 4 中的EventLoop)中执行。所有的出站(下游)事件都由调用线程处理,其可能是 I/O 线程也可能是别的线程
JDK的任务调度
JDK 提供了 java.util.concurrent 包,它定义了 interface ScheduledExecutorService,下面是对源码的类注释和代码示例
An ExecutorService that can schedule commands to run after a given delay, or to execute periodically.
//创建一个其线程池具有 10 个线程的ScheduledExecutorService
ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
ScheduledFuture<?> future = executor.schedule(
//创建一个 Runnable,以供调度稍后执行
new Runnable() {
@Override
public void run() {
//该任务要打印的消息
System.out.println("Now it is 60 seconds later");
}
//调度任务在从现在开始的 60 秒之后执行
}, 60, TimeUnit.SECONDS);
//...
//一旦调度任务执行完成,就关闭 ScheduledExecutorService 以释放资源
executor.shutdown();
虽然ScheduledExecutorService API 是直截了当的,但是在高负载下它将带来性能上 的负担
EventLoop 的任务调度
ScheduledExecutorService 的实现作为线程池管理的一部分,将会有额外的线程创建。如果有大量任务被紧凑地调度,那么这将成为一个瓶颈。Netty 通过Channel的EventLoop实现任务调度解决了这一问题
Channel ch = CHANNEL_FROM_SOMEWHERE;
ScheduledFuture<?> future = ch.eventLoop().schedule(
//创建一个 Runnable以供调度稍后执行
new Runnable() {
@Override
public void run() {
//要执行的代码
System.out.println("60 seconds later");
}
//调度任务在从现在开始的 60 秒之后执行
}, 60, TimeUnit.SECONDS);
经过 60 秒之后,Runnable实例将由分配给Channel的EventLoop执行
如果要调度任务以每隔 60 秒执行一次,则使用scheduleAtFixedRate()方法
Creates and executes a periodic action that becomes enabled first after the given initial delay, and subsequently with the given period; that is executions will commence after initialDelay then initialDelay+period, then initialDelay + 2 * period, and so on. If any execution of the task encounters an exception, subsequent executions are suppressed. Otherwise, the task will only terminate via cancellation or termination of the executor. If any execution of this task takes longer than its period, then subsequent executions may start late, but will not concurrently execute.
Channel ch = CHANNEL_FROM_SOMEWHERE;
ScheduledFuture<?> future = ch.eventLoop().scheduleAtFixedRate(
//创建一个 Runnable,以供调度稍后执行
new Runnable() {
@Override
public void run() {
//这将一直运行,直到 ScheduledFuture 被取消
System.out.println("Run every 60 seconds");
}
//调度在 60 秒之后,并且以后每间隔 60 秒运行
}, 60, 60, TimeUnit.SECONDS);
要想取消或者检查(被调度任务的)执行状态,可以使用每个异步操作所返回的 Scheduled- Future
Channel ch = CHANNEL_FROM_SOMEWHERE;
//调度任务,并获得所返回的ScheduledFuture
ScheduledFuture<?> future = ch.eventLoop().scheduleAtFixedRate(
new Runnable() {
@Override
public void run() {
System.out.println("Run every 60 seconds");
}
}, 60, 60, TimeUnit.SECONDS);
// Some other code that runs...
boolean mayInterruptIfRunning = false;
//取消该任务,防止它再次运行
future.cancel(mayInterruptIfRunning);
Netty的EventLoop扩展了ScheduledExecutorService,所以它提供了使用JDK实现可用的所有方法,包括在前面的示例中使用到的schedule()和scheduleAtFixedRate()方法。所有操作的完整列表可以在ScheduledExecutorService的 Javadoc中找到
线程管理
Netty线程模型能够对当前执行的Thread的身份的进行确定,我们可以在SingleThreadEventExecutor类中找到这个方法
@Override
public boolean inEventLoop(Thread thread) {
return thread == this.thread;
}
如果(当前)调用线程正是支撑EventLoop的线程,那么所提交的代码块将会被(直接)执行。否则,EventLoop将调度该任务以便稍后执行,并将它放入到内部队列中。当EventLoop 下次处理它的事件时,它会执行队列中的那些任务/事件
异步传输的线程分配
异步传输实现只使用了少量的EventLoop(以及和它们相关联的Thread), 而且在当前的线程模型中,它们可能会被多个Channel所共享。这使得可以通过尽可能少量的Thread来支撑大量的Channel,而不是每个Channel分配一个Thread
EventLoopGroup负责为每个新创建的Channel分配一个EventLoop。在当前实现中, 使用顺序循环(round-robin)的方式进行分配以获取一个均衡的分布,并且相同的 EventLoop 可能会被分配给多个Channel,所以对于所有相关联的 Channel 来说, ThreadLocal都将是一样的
一旦一个 Channel 被分配给一个 EventLoop,它将在它的整个生命周期中都使用这个 EventLoop(以及相关联的Thread)
阻塞传输的线程分配
每一个Channel都将被分配给一个EventLoop(以及它的Thread)
每个 Channel 的 I/O 事件仍然都将只会被一个 Thread (用于支撑该Channel的EventLoop的那个Thread)处理
这种相似的设计模式使得我们能够方便的在nio和oio之间切换,而不用对我们的业务逻辑代码进行大的改动
Netty学习摘记 —— 再谈EventLoop 和线程模型的更多相关文章
- Netty学习摘记 —— 再谈引导
本文参考 本篇文章是对<Netty In Action>一书第八章"引导"的学习摘记,主要内容为引导客户端和服务端.从channel内引导客户端.添加ChannelHa ...
- Netty学习摘记 —— 再谈ChannelHandler和ChannelPipeline
本文参考 本篇文章是对<Netty In Action>一书第六章"ChannelHandler和ChannelPipeline",主要内容为ChannelHandle ...
- 【Netty】EventLoop和线程模型
一.前言 在学习了ChannelHandler和ChannelPipeline的有关细节后,接着学习Netty的EventLoop和线程模型. 二.EventLoop和线程模型 2.1. 线程模型 线 ...
- Netty学习摘记 —— 初步认识Netty核心组件
本文参考 我在博客内关于"Netty学习摘记"的系列文章主要是对<Netty in action>一书的学习摘记,文章中的代码也大多来自此书的github仓库,加上了一 ...
- Netty 框架学习 —— EventLoop 和线程模型
EventLoop 接口 Netty 是基于 Java NIO 的,因此 Channel 也有其生命周期,处理一个连接在其生命周期内发生的事件是所有网络框架的基本功能.通常来说,我们使用一个线程来处理 ...
- Netty学习摘记 —— 深入了解Netty核心组件
本文参考 本篇文章是对<Netty In Action>一书第三章"Netty的组件和设计"的学习摘记,主要内容为Channel.EventLoop.ChannelFu ...
- Netty学习摘记 —— Netty客户端 / 服务端概览
本文参考 本篇文章是对<Netty In Action>一书第二章"你的第一款 Netty 应用程序"的学习摘记,主要内容为编写 Echo 服务器和客户端 第一款应用程 ...
- Netty实战七之EventLoop和线程模型
简单地说,线程模型指定了操作系统.编程语言.框架或者应用程序的上下文中的线程管理的关键方面.Netty的线程模型强大但又易用,并且和Netty的一贯宗旨一样,旨在简化你的应用程序代码,同时最大限度地提 ...
- Netty学习摘记 —— 心跳机制 / 基于分隔符和长度的协议
本文参考 本篇文章是对<Netty In Action>一书第十一章"预置的ChannelHandler和编解码器"的学习摘记,主要内容为通过 SSL/TLS 保护 N ...
随机推荐
- 【C# .Net GC】手动监视和控制对象的生命周期(GCHandle)
这个话题还未做详细研究,暂时用不到,只是粗略看了一下. 使用System.Runtime.InteropServices.GCHandle类来手动控制对象的生命周期 (个人感觉这里可能有一些问题... ...
- idea Transparent-native-to-ascii 是否需要勾选?
目录 首先看一下官方对该选项的解释: 第一段是说标准的Java api是用ISO 8859-1编码.properties文件的,所以如果你在properties文件中可以使用转义序列表示没在这个编码中 ...
- 8.StringTable(字符串常量池)
一.String的基本特性 String:字符串,使用一对 "" 引起来表示 String s1 = "atguigu" ; // 字面量的定义方式 Strin ...
- Python音频操作+同时播放两个音频
对于python而言,音频的操作可以使用pygame包中的sound 和 music对象,本博客主要讲解这两个对象. 1.sound对象 Sound对象适合处理较短的音乐,如OGG和WAV格式的音频文 ...
- Python简单入门心得(一)
很久之前就对Python感兴趣了,但是一直没时间学习,最近两天还有点时间,于是网上看了下视频,Python不愧是强类型的编程语言,对每一行的缩进的都有很严格的要求,比如一个判断,如果条件语句else不 ...
- Linux CentOS7.X-目录操作命令
一.安装vim 由于安装Centos7MINI版本里面没有vim命令,只有vi命令,所以安装vim命令,默认系统只带了vim-minimal.x86_64包,需要安装其他的3个包才能用vim命令 1. ...
- 怎么样在手机调试js,jq,html,如何在手机上调试js,javascript
方法 直接在html中引入vconsole.js文件, 然后在js脚本中使用console.log('调试内容'); 即可看到如下效果,还可以在network里面看到ajax请求 我把js文件传到博客 ...
- git常用命令及问题
Git基本操作 git init 创建新的git仓库 git clone [url] 使用 git clone 拷贝一个 Git 仓库到本地 git status 查看工作区 git stash li ...
- PointNet: Deep Learning on Point Sets for 3D Classification and Segmentation
摘要 点云是一种重要的几何数据结构类型.由于其不规则的格式,大多数研究人员将此类数据转化为常规的三维体素网格或图像集合.然而,这使数据变得不必要的庞大,并导致问题.在本文中,我们设计了一种新型的直接处 ...
- Web网站建站过程(白嫖)——域名
目录 1.域名注册商(选一个吧) 2.域名注册 没有域名建啥站? 1.域名注册商(选一个吧) 到时候你们就会想起: ...... 但是我们不用上面的,因为上面的太费Q,我们要用的是-- 2.域名注册 ...