12 线程模型
转:http://www.tianshouzhi.com/api/tutorials/netty/334
netty是被设计用于支持reactor
线程模型的。注意,这里用的是支持,而不是使用了netty,就一定会是reactor线程模型。不过大多数情况下,我们在编写netty应用的时候,很多人就会忽略这些问题,糊里糊涂的用了netty,也不知道自己编写的netty应用属于那种reactor线程模型。
关于reactor线程模型理解的深刻程度,直接影响了netty服务端程序开发的效率和性能。本文试图通过实战案例(基于netty4.x),帮助你理解在实际使用netty开发的过程中,如何更好的应用reactor线程模型。
1 基本并发编程模型
在讲解reactor线程模型之前,我们需要先对基本并发编程模型:串行工作者模型、并发工作者模型进行讲解,因为netty中的reactor线程模型是在这个基础之上演进的,并且,根据我们代码编写的方式,我们的netty应用也有可能是串行工作者模型或者并行工作者模型的。
总的来说:
串行工作者模型和并行工作者模型关注的是将任务划分为2个阶段:1、任务的接受阶段 2、任务的处理阶段;
而reactor线程模型关注的是上述第二个阶段:任务在处理的过程中,继续划分为多个步骤进行处理下面依次进行讲解
1.1 串行工作者模型
我们以一个典型的任务处理流程,来说明为什么要将任务的接受流程与处理流程划分开来
在这个例子中,一个worker线程来处理用户提交的任务,任务的处理步骤粗略的分为:接受任务和处理任务两个阶段。当worker接受到一个任务之后,就立刻进行处理,也就是说任务接受和任务处理是在同一个worker线程中进行的,没有进行区分。这样做存在一个很大的问题是,必须要等待某个task处理完成之后,才能接受处理下一个task。
而通常情况下,任务的处理过程会比任务的接受流程慢得多。例如在处理任务的时候,我们可能会需要访问远程数据库,这属于一种网络IO。通常情况下IO操作是比较耗时的,这直接影响了下一个任务的接受,而且通常在IO操作的时候,CPU是比较空闲的,白白浪费了资源。
因此我们可以考虑将任务的接受与处理分为两个线程进行处理,一个只负责接受任务,一个只负责处理任务。
这就演化出了第一个线程模型:串行工作者模型。如下所示:
在这种情况下,接受任务的线程称之为AcceptThread,其将接受到的任务放到一个任务队列中,因此能立即返回接受下一个任务。而worker线程不断的从这个队列中取出任务进行异步执行。
目前这种情况存在一个很大的问题,在于任务处理的太慢,导致队列里积压的任务数量越来愈大,任务不能得到及时的执行。所以我们可以用多个worker thread来处理任务。这就是串行工作者模型的并发版本-并行工作者模型
。
1.2 并行工作者模型
在并行工作者模型中,有一个accpet thread,多个worker thread
,因为worker thread的功能都相同,所以我们通常会将其划分到一个组中(worker thread group) 。
在具体实现上,并行工作者线程模型有两种设计方式,以下分别进行介绍。
并行工作者线程模型设计方式一:基于公共任务队列
accept thread将接受到的任务放到任务队列中,worker thread group中的多个worker thread 并行的从公共的队列中拉取任务进行处理
image.png
熟悉java线程池 的用户可能已经发现,可以用ThreadPoolExecutor 来实现右半部分的功能,因为ThreadPoolExecutor 就是多个线程从一个公共的任务队列中拉取任务进行执行。通过在main线程中接受任务,将任务提交到线程池中,即可以完成上述线程模型。
并行工作者线程模型设计方式二:每个worker thread维护自己的任务队列
在第一种方式中,由于多个worker线程同时从一个公共的任务队列中拉取任务进行处理,因此必须要进行加锁,因而影响了效率。因此又有了下面一种设计方式:reactor thread直接将任务转发给各个worker thread,每个worker thread内部维护一个队列来处理
,如下图
这种方式的设计,避免的锁竞争,因为每个worker thread都从各自的队列中取出任务进行执行。实际上,netty的实现中,就是为每个worker thread维护了一个队列。
需要注意的是:由于现在是accpet thread直接给各个worker thread转发任务,任务分配的平均的责任就落到了reactor thread的身上。
2 Reactor线程模型
首先要需要说明的是,reactor线程模型并不是netty所独有,其是一种并发编程模型,更确切的或者说一种思想,其具有的是指导意义,开发者需要在这种编程模型思想的指导下,结合自己的实际场景,来进行合理的设计。在不同的场景下,可能设计出来的reactor线程模型是不一样的,例如scala中的akka框架,就是基于reactor线程模型的思想设计的。换句话说,netty只是结合了nio网络编程的特点,合理的应用了reactor线程模型。
关于netty是如何合理的利用reactor线程模型,将在之后讲解。
现在我们还是需要简单的介绍一下有哪些典型的reactor线程模型设计方式。回顾串行工作者模型和并行工作者模型,它们主要的关注点是:划分任务的接受阶段与任务的处理阶段。也正是因为如此,我们通常将接受任务的线程称之为Accpet Thread
。而任务的处理过程都是一个线程(worker thread
)内完成的。
reactor线程模型关注的是:任务接受之后,对处理过程继续进行切分,划分为多个不同的步骤,每个步骤用不同的线程来处理,也就是原本由一个线程处理的任务现在由多个线程来处理,每个线程在处理完自己的步骤之后,还需要将任务转发到线程继续进行处理。为了进行区分,在reactor线程模型中,处理任务并且分发的线程,不再称之为worker thread,而是reactor thread
。
2.1 单线程reactor线程模型
下图演示了单线程reactor线程模型,之所以称之为单线程,还是因为只有一个accpet Thread接受任务,之后转发到reactor线程中进行处理。两个黄色框表示的是Reactor Thread Group,里面有多个Reactor Thread。一个Reactor Thread Group中的Reactor Thread功能都是相同的,例如第一个黄色框中的Reactor Thread都是处理拆分后的任务的第一阶段,第二个黄色框中的Reactor Thread都是处理拆分后的任务的第二步骤。任务具体要怎么拆分,要结合具体场景,下图只是演示作用。一般来说,都是以比较耗时的操作(例如IO)为切分点。
特别的,如果我们在任务处理的过程中,不划分为多个阶段进行处理的话,那么单线程reactor线程模型就退化成了并行工作和模型。事实上,可以认为并行工作者模型,就是单线程reactor线程模型的最简化版本。
2.2 多线程reactor线程模型
所谓多线程reactor线程模型,无非就是有多个accpet线程,如下图中的虚线框中的部分。
image.png
2.3 混合型reactor线程模型
混合型reactor线程模型,实际上最能体现reactor线程模型的本质:
-
将任务处理切分成多个阶段进行,每个阶段处理完自己的部分之后,转发到下一个阶段进行处理。不同的阶段之间的执行是异步的,可以认为每个阶段都有一个独立的线程池。
-
不同的类型的任务,有着不同的处理流程,划分时需要划分成不同的阶段。如下图蓝色是一种任务、绿色是另一种任务,两种任务有着不同的执行流程
image.png