netty的线程池-----揭示了使用两个线程池的原因
线程模型是Netty的核心设计,设计地很巧妙,之前项目中有一块处理并发的设计和Netty的Eventloop单线程设计类似,效果得到了实证。
Netty5的类层次结构和之前的版本变化很大,网上也有很多文章写Netty的线程模型,Reactor模式,比如这篇http://blog.csdn.net/xiaolang85/article/details/37873059, 应该是引自《Netty权威指南》,写得比较全面,但是有几个关键的概念没讲清楚。
这篇文章只讲Netty5线程模型最重要的几个关键点
第一个概念是如何理解NioEventLoop和NioEventLoopGroup:NioEventLoop实际上就是工作线程,可以直接理解为一个线程。NioEventLoopGroup是一个线程池,线程池中的线程就是NioEventLoop。Netty设计这几个类的时候,层次结构挺复杂,反而让人迷惑。
还有一个让人迷惑的地方是,创建ServerBootstrap时,要传递两个NioEventLoopGroup线程池,一个叫bossGroup,一个叫workGroup。《Netty权威指南》里只说了bossGroup是用来处理TCP连接请求的,workGroup是来处理IO事件的。
这么说是没错,但是没说清楚bossGroup具体如何处理TCP请求的。实际上bossGroup中有多个NioEventLoop线程,每个NioEventLoop绑定一个端口,也就是说,如果程序只需要监听1个端口的话,bossGroup里面只需要有一个NioEventLoop线程就行了。
在上一篇文章介绍服务器端绑定的过程中,我们看到最后是NioServerSocketChannel封装的Java的ServerSocketChannel执行了绑定,并且执行accept()方法来创建客户端SocketChannel的连接。一个端口只需要一个NioServerSocketChannel即可。
- EventLoopGroup bossGroup = new NioEventLoopGroup();
- EventLoopGroup workerGroup = new NioEventLoopGroup();
- try {
- ServerBootstrap b = new ServerBootstrap();
- b.group(bossGroup, workerGroup)
- .channel(NioServerSocketChannel.class)
- .option(ChannelOption.SO_BACKLOG, 1024)
- .childHandler(new ChildChannelHandler());
- ChannelFuture f = b.bind(port).sync();
- f.channel().closeFuture().sync();
- } finally {
- bossGroup.shutdownGracefully();
- workerGroup.shutdownGracefully();
- }
- protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
- if (nThreads <= 0) {
- throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
- }
- if (executor == null) {
- executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
- }
- children = new EventExecutor[nThreads];
- for (int i = 0; i < nThreads; i ++) {
- boolean success = false;
- try {
- children[i] = newChild(executor, args);
- success = true;
- } catch (Exception e) {
- // TODO: Think about if this is a good exception type
- throw new IllegalStateException("failed to create a child event loop", e);
- } finally {
第二个概念是每个NioEventLoop都绑定了一个Selector,所以在Netty5的线程模型中,是由多个Selecotr在监听IO就绪事件。而Channel注册到Selector。
举个例子,比如有100万个连接连到服务器端。平时的写法可能是1个Selector线程监听所有的IO就绪事件,1个Selector面对100万个连接(Channel)。
而如果使用了1000个NioEventLoop的线程池来说,1000个Selector面对100万个连接,每个Selector只需要关注1000个连接(Channel)
- public final class NioEventLoop extends SingleThreadEventLoop {
- /**
- * The NIO {@link Selector}.
- */
- Selector selector;
- private SelectedSelectionKeySet selectedKeys;
- private final SelectorProvider provider;
第三个概念是一个Channel绑定一个NioEventLoop,相当于一个连接绑定一个线程,这个连接所有的ChannelHandler都是在一个线程中执行的,避免的多线程干扰。更重要的是ChannelPipline链表必须严格按照顺序执行的。单线程的设计能够保证ChannelHandler的顺序执行。
- public interface Channel extends AttributeMap, Comparable<Channel> {
- /**
- * Return the {@link EventLoop} this {@link Channel} was registered too.
- */
- EventLoop eventLoop();
第四个概念是一个NioEventLoop的selector可以被多个Channel注册,也就是说多个Channel共享一个EventLoop。EventLoop的Selecctor对这些Channel进行检查。
这段代码展示了线程池如何给Channel分配EventLoop,是根据Channel个数取模
- public EventExecutor next() {
- return children[Math.abs(childIndex.getAndIncrement() % children.length)];
- }
- private void processSelectedKeysOptimized(SelectionKey[] selectedKeys) {
- for (int i = 0;; i ++) {
- // 逐个处理注册的Channel
- final SelectionKey k = selectedKeys[i];
- if (k == null) {
- break;
- }
- final Object a = k.attachment();
- if (a instanceof AbstractNioChannel) {
- processSelectedKey(k, (AbstractNioChannel) a);
- } else {
- @SuppressWarnings("unchecked")
- NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
- processSelectedKey(k, task);
- }
- if (needsToSelectAgain) {
- selectAgain();
- // Need to flip the optimized selectedKeys to get the right reference to the array
- // and reset the index to -1 which will then set to 0 on the for loop
- // to start over again.
- //
- // See https://github.com/netty/netty/issues/1523
- selectedKeys = this.selectedKeys.flip();
- i = -1;
- }
- }
- }
理解了这4个概念之后就对Netty5的线程模型有了清楚的认识:
在监听一个端口的情况下,一个NioEventLoop通过一个NioServerSocketChannel监听端口,处理TCP连接。后端多个工作线程NioEventLoop处理IO事件。每个Channel绑定一个NioEventLoop线程,1个NioEventLoop线程关联一个selector来为多个注册到它的Channel监听IO就绪事件。NioEventLoop是单线程执行,保证Channel的pipline在单线程中执行,保证了ChannelHandler的执行顺序。
下面这张图来之http://blog.csdn.net/xiaolang85/article/details/37873059, 基本能说清楚。

netty的线程池-----揭示了使用两个线程池的原因的更多相关文章
- SessionFactory是线程安全的吗?Session是线程安全的吗?两个线程能共享一个Session吗?
(1)SessionFactory对应Hibernate的一个数据存储的概念,它是线程安全的,可以被多个线程并发访问.SessionFactory一般只会在启动的时候构建.对于应用程序,最好将Sess ...
- JavaSE中线程与并行API框架学习笔记——线程为什么会不安全?
前言:休整一个多月之后,终于开始投简历了.这段时间休息了一阵子,又病了几天,真正用来复习准备的时间其实并不多.说实话,心里不是非常有底气. 这可能是学生时代遗留的思维惯性--总想着做好万全准备才去做事 ...
- Qt 进程和线程之三:线程同步、可重入与线程安全
一.同步线程方法 使用线程的目的是允许代码并行运行,但是有时线程必须停止并等待其他线程.例如,如果两个线程试图同时写入相同的变量,结果是不确定的,所以需要同步线程.同步线程是一种保护共享资源等数据的常 ...
- AsyncTask两种线程池
AsyncTask两种线程池 http://bbs.51cto.com/thread-1114378-1.html (API 3.0以后): 1.THREAD_POOL_EXECUTOR, ...
- MyCAT 在 Cobar 的基础上,完成了彻底的 NIO 通讯,并且合并了两个线程池
研读: 1.http://www.mycat.io <Mycat权威指南> 第 2 章 Mycat 前世今生: 浏览: 深度认识 Sharding-JDBC:做最轻量级的数据库中间层 - ...
- 你创建线程池最好分为两种线程池,io密集型线程池,或者cpu密集型线程池
你创建线程池最好分为两种线程池,io密集型线程池,或者cpu密集型线程池. 否则,如果只用一个线程池的话,不管是iO密集的线程,或者cpu消耗大的都放在同一个线程池的话,会发生线程池被撑满的情况
- Mina、Netty、Twisted一起学(十):线程模型
要想开发一个高性能的TCP服务器,熟悉所使用框架的线程模型非常重要.MINA.Netty.Twisted本身都是高性能的网络框架,如果再搭配上高效率的代码,才能实现一个高大上的服务器.但是如果不了解它 ...
- Java线程池的原理及几类线程池的介绍
刚刚研究了一下线程池,如果有不足之处,请大家不吝赐教,大家共同学习.共同交流. 在什么情况下使用线程池? 单个任务处理的时间比较短 将需处理的任务的数量大 使用线程池的好处: 减少在创建和销毁线程上所 ...
- 浅谈线程池(中):独立线程池的作用及IO线程池
原文地址:http://blog.zhaojie.me/2009/07/thread-pool-2-dedicate-pool-and-io-pool.html 在上一篇文章中,我们简单讨论了线程池的 ...
随机推荐
- wait3和wait4函数(转)
wait3和wait4函数除了可以获取子进程状态转变信息外,还可以获得子进程的资源使用信息. pid_t wait3 ( int *status, int option, struct rusage ...
- React(0.13) 定义一个使用动画
<!DOCTYPE html> <html> <head> <title>React JS</title> <script src=& ...
- HTTP 请求头 Header
HTTP 请求头 Header HTTP请求头概述 (HttpServletRequest) HTTP客户程序(例如浏览器),向服务器发送请求的时候必须指明请求类型(一般是GET或者POST或者HEA ...
- Docker未启动错误:Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
此问题是因为Docker安装后未启动所致,执行以下命令启动docker: systemctl start docker.service 具体日志如下: Connecting to ... Connec ...
- spring-data-redis读写分离
在对Redis进行性能优化时,一直想对Redis进行读写分离.但由于项目底层采用spring-data-redis对redis进行操作,参考spring官网却发现spring-data-redis目前 ...
- 【转】写给支持和反对《完全用Linux工作》的人们
早就有人问起我的学习情况,问我有没有找到理想的研究环境.我却总是弄一些小动物,要不就是好玩的内容在这上面.真是惭愧,因为一直觉得自己还没有什么发言权,一直觉得是不是自己搞错了.不过来了 Cornell ...
- zookeeper注册中心安装(单机版)
下载zookeeper-3.4.9.tar.gz wget http://apache.fayea.com/zookeeper/zookeeper-3.4.9/zookeeper-3.4.9.tar. ...
- Socket编程知识必学
端口号常识: 端口号被从1 开始分配. 通常端口号超出255 的部分被本地主机保留为私有用途. 1到255 之间的号码被用于远程应用程序所请求的进程和网络服务. 每个网络通信循 ...
- django -- 用包来组织数据库模型
默认情况下一个django app的所有模型都保存在一个叫models.py的文件中.这样事实是不方便管理的: 通过包来组织模型是比较方便的. 一.第一步:删除models.py: rm -rf mo ...
- pm2 设置开机启动
一.官方文档: 官方相关文档:http://pm2.keymetrics.io/docs/usage/startup/#generating-a-startup-script 二.具体操作过程如下: ...