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

文中所讲基本都是以非阻塞IO、异步IO为基础。对于阻塞式IO,下面的编程模型几乎都不适用mysql

Reactor三种线程模型

单线程模型

单个线程以非阻塞IO或事件IO处理全部IO事件,包括链接、读、写、异常、关闭等等。单线程Reactor模型基于同步事件分离器来分发事件,这个同步事件分离器,能够看作是一个单线程的while循环。下图描述了单线程模型的处理过程,看起来与网上大部分资料的图片不一样,但本质是相同的。react

 

注意上面的Selector之因此会有OP_ACEEPT事件,是由于在单线程模型中,Selector轮询的是监听套接字与已链接客户端套接字的全部IO事件。sql

单线程处理全部IO事件的弊端很明显。没能利用计算机CPU多核的特性,一个线程某个时刻只能处理单个IO事件,此时若是有其余描述符有IO事件就绪(如来了一个新的链接),这些IO事件将暂时得不处处理。编程

C++框架libevent中,基于event_base_loop作消息轮询,使用event_base_dispatch来分发IO消息,本质上是对上述模型的封装。若是不使用evthread_use_pthreads,则其默认就是单线程模型处理请求。多线程

多线程模型

一个线程/进程接收链接、一组线程/进程处理IO读写事件。也就是将accept的线程与处理读、写等IO事件的线程分离,而且使用m多个线程、使用非阻塞IO或者事件IO来处理n个套接字的IO事件,这里的n通常远大于m,m通常取CPU逻辑核心数的1-3倍,而套接字数n则取决于请求数和进程能够打开的最大描述符个数。下图是多线程模型并发

能够看到,这里把客户端的已链接套接字,转交给某个IO线程以后,由此线程轮询处理其以后的全部IO事件,这实际参考了netty4的线程模型设计。实际reactor的多线程模型,并不须要将已链接套接字绑定在某个线程上,也能够统一放在链接池中,由多个IOWork线程从池中取链接进行轮询并处理,但这样会复杂不少,并且容易出问题,好比说不一样线程从同一个channel收到了write事件,这就相似惊群问题了;而且多线程并发操做同一个channel,后续极可能须要你讲IO事件进行同步,与其如此,不如直接将channel绑定到一个线程,让channel上触发与处理IO事件逻辑上同步。netty3中channel(已链接套接字)入站事件由固定线程处理,出站事件由触发的线程处理,netty4中修改了设计,将channel绑定到固定的eventloop(线程)。框架

另一点,每一个已链接套接字的IO事件由固定线程处理,不表明事件也必定由此线程触发,偏偏相反,实际业务中,读(入站)事件来自于客户端写数据触发,而写(出站)事件每每由别的线程触发,例如在发起一个异步mysql操做完成以后,在异步回调线程中写结果数据来触发套接字的出站。异步

主从多线程模型

一组线程/进程接收链接、一组线程/进程处理IO读写事件。它与多线程模型的主要区别在于其使用一组线程或进程在一个共享的监听套接字上accept链接。这么作的缘由是为了应付单个线程/进程不足以快速处理内核中监听套接字的已链接套接字队列(并发量极大)的状况。以下socket

 

主从多线程模型,有可能引发惊群效应。不过这个问题已经渐渐被规避,内核能够保证链接只被惟一一个accept调用所获取,其他对此链接的accept调用将失败。oop

Netty支持的线程模型

Netty支持单线程、多线程模型、主从多线程模型。但经本人屡次测试、调试发现,ServerBootstrap默认不会使用主从多线程模型。虽然server支持设置EventLoopGroup(多个EventLoop)。但实际对于一个本地地址(IP+端口)进行accept,netty只会将监听套接字绑定到第一个EventLoop上,故只会建立一个线程处理。

按本人的理解,Boss EventLoopGroup(Master EventLoopGroup,参数nThreads不为1)的做用主要用在对共享的监听套接字或者多个本地地址监听,对多个本地地址进行监听通常表示一个JVM中有多个server,即有多个ServerBootStrap,这时,Boss EventLoopGroup能够经过共享给这多个ServerBootStrap起到做用(建立多个boss/master Thread)

如下面的代码为例MASTER_THREAD_CNT为4但netty实际只会使用第一个EventLoop,只会给第一个EventLoop建立线程

 

调试跟踪源码,能够明白netty的逻辑。

在ServerBootstrap继承的initAndRegister方法中,调用MultithreadEventLoopGroup#register方法,此方法调用this.next获取当前索引的下一个(索引位0,便是第一个)EventLoop。

而后register方法进一步调用register方法,在register中执行eventLoop.execute,这里才会真正为监听套接字建立第一个轮询线程。

 

问题就在于在ServerBootstrap上调用bind方法,初始化监听socket并绑定EventLoop时,是调用的next方法。所以netty只会初始化第一个MasterEventLoop,若是想将MasterEventLoopGroup中的每一个EventLoop都初始化,很显然,须要重复绑定多个监听套接字或者屡次绑定一个可共享的套接字。

相关文章
相关标签/搜索