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 ...
随机推荐
- vmware启动报错:Failed to load SELinux policy. Freezing
修改 : SELINUX=disabled 正确 误修改: SELINUXTYPE=disabled 错误 导致无法开机 错误结果 重启后 机器就报 Failed to load SELi ...
- 哈工大 计算机系统 实验七 TinyShell
所有实验文件可见github 计算机系统实验整理 实验报告 实 验(七) 题 目 TinyShell 微壳 计算机科学与技术学院 目 录 第1章 实验基本信息 - 4 - 1.1 实验目的 - 4 - ...
- 《MySQL实战45讲》个人笔记-基础篇
拜读了林晓斌大佬的<MySQL实战45讲>,特意做个知识点总结,以便后期回忆. 01.基础架构:一条SQL查询语句是如何执行的? Server 层包括连接器.查询缓存.分析器.优化器.执行 ...
- Dubbo是什么?核心总结
Dubbo --是SOA架构的具体的实现框架! 2.1 Dubbo简介 Apache Dubbo是一款高性能的Java RPC框架.官网地址:[http://dubbo.apache.org] dub ...
- 『德不孤』Pytest框架 — 9、Pytest测试报告
目录 1.pytest-html插件 2.Allure测试报告 (1)Allure框架说明 (2)Allure框架的使用 1.pytest-html插件 Pytest可以通过命令行方式,生成xml/h ...
- 矩池云上使用Visdom可视化图像说明
租用机器添加默认端口 点击展开高级选项 点击添加端口配置 添加Visdom默认端口,选择 http 端口填入 8097 进入环境安装并使用 JupyterLab 链接 是本次实验用来安装实验的工具 H ...
- ElasticSearch集成SpringBoot与常见使用方法
目录 一.导包 二.核对导入的ES版本 修改导入版本 三.写配置类 四.开始测试 索引操作 1.创建索引 2.查看索引是否存在 3.删除索引 文档操作 1.添加文档 2.查看文档是否存在 3.修改文档 ...
- Java运算符 算术运算法
运算符 算术运算法:+,-,*,/,%,++,– 复制运算符:= 关系运算符:>,<,>=,<=,==,!= instanceof 逻辑运算符:&&,||,! ...
- LGP3726题解
确实牛逼......这个转化我反正肯定想不到... 考虑 \(a=b\) 的情况.发现出了平局之外都是一半赢一半输.可以得到此时的答案为: \[\frac{2^{a+b}-\sum_{i=0}^{a} ...
- LOJ6485题解
应该是经典题之一了. \[[n|k]=\frac 1 n\sum_{i=0}^{n-1}w_n^{ik} \] 有这个就可以算了. \[\sum_{i=0}^n\binom n i x^ia_{i \ ...