Reactor Proactor 两个IO多路复用的方法

前端之家收集整理的这篇文章主要介绍了Reactor Proactor 两个IO多路复用的方法前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

今天看了一篇文章,感觉不错,就决定翻译一下。原文:http://www.artima.com/articles/io_design_patterns2.html

译文:

比较两个高性能的 I/O 设计模式

反应器和前摄器: 两个 I/O 多路复用的方法

总体上来讲, I/O 多路复用机制依赖于一个事件多路解调器,多路解调器是一个把一定数量的源的IO事件分发到合适的读写事件处理器(handler)。 开发者提供事件处理器或者回调函数,并注册到感兴趣的事件。事件解调器发送要求的事件给事件处理器。


有两个事件解调器相关的设计模式:反应器和前摄器。反应器应用于同步I/O,前摄器应用于异步I/O. 反应器等待一个文件描述符或者socket变成可读写状态。多路解调器把事件通知给合适的事件处理器,事件处理器负责真正的读写。


前摄器就相反,事件处理器(或者代表事件处理器的事件解调器)初始化异步读写操作。IO操作本身是由操作系统来执行的。传递给操作系统的参数包括用户定义的缓冲地址,用于读取数据或者写入数据。事件解调器等待IO操作的完成,并且把这些事件告诉合适的事件处理器(handler)。比如:在Windows上,一个事件处理器可以初始化一个IO(微软公司的重叠技术)操作,事件解调器会等待IO完成事件。这个典型的异步模式的实现依赖于操作系统的异步API,我们把这个实现叫做“系统级别”或者“真异步”,因为应用完全的依赖于操作系统来执行真正的IO.


有一个例子可以帮助大家理解反应器和前摄器的区别。我们只关心读操作,写操作也是类似。先是反应器的读操作:

1. 一个事件处理器(handler)声明对一个socket的可读事件感兴趣

2. 事件解调器(如select)等待事件

3. 一个可读事件来了,事件解调器(select)会被唤醒,并且调用相应的事件处理器

4. 事件处理器执行真正的读操作,处理读到的数据,重新注册IO事件,然后把控制权返回给分发器。

作为比较,接下来是前摄器的读操作:

1. 一个事件处理器初始化一个异步读操作(注意:操作系统必须支持异步IO)。在这里,事件处理器对IO可读事件不感兴趣,但是对读操作完成事件感兴趣。

2. 事件解调器等待读操作完成的事件。

3. 当事件解调器在等待的时候,操作系统在内核模式执行读操作,并把数据放入用于定义的缓冲区,然后通知事件解调器读操作完成了。

4. 事件解调器调用合适的事件处理器。

5. 事件处理器处理用户定义的缓冲区里面的数据,并且开始一个新的异步操作,之后把控制权返回给事件解调器。


目前的应用

Douglas Schmidt等人开发的开源C++开发框架ACE,提供了一个很庞大的平台无关,支持并行(线程,互斥等)的开发框架。在上层,ACE提供了两组独立的类:ACE反应器的实现和ACE前摄器的实现。这两组类都是平台无关的,但是它们提供不同的接口。


Windows提供了一个非常有效的基于操作系统级别的异步API,所以ACE前摄器模式在Windows上的性能很好而且强壮。


不幸的是,并不是所有的操作系统都提供强壮的异步IO支持。实际上,许多Unix系统并不支持。因此,在Unix系统上,ACE反应器更受欢迎(目前的Unix系统并没有强壮的socket异步支持)。结果是,为了追求各个系统上的最好的性能,网络应用开发者需要维护两套不同的代码:Windows上的ACE前摄器和Unix上的ACE反应器模式。


就像我们前面提到过的,“真异步”前摄器设计模式需要操作系统级别的支持。因为事件处理器的差异和操作系统的差异,很难为反应器和前摄器创建一个通用的统一的接口。所以很难创建一个轻便的开发框架来封装接口和操作系统的差异。


建议的方案

在这个小节里,我们建议一个方案来挑战对反应器和前摄器IO模式的轻便框架的设计。为了证明这个方案,我们会通过把读写操作从事件处理器移动到解调器的方式把反应器的IO解调方案改变成一个模拟的异步IO(这就是“模拟异步”方案)。下面的例子说明了一个读操作的转化:

1. 一个事件处理器声明对一个IO事件(可读事件)感兴趣,并且提供解调器包括用户定义的缓冲区地址,需要读取多少字节等。

2. 分发器等待事件(如select)

3. 当一个事件来临的时候,分发器会被唤醒。分发器执行一个非阻塞读操作并且完成后调用合适的事件处理器。

4. 事件处理器处理用户缓冲区里面的数据,然后声明新的感兴趣的事件,包括用户定义的缓冲区和需要读取的字节数。之后事件处理器把控制权交给分发器。

就像我们看到的,通过在解调器里面增加功能,我们可以把一个反应器模式转划成一个前摄器模式。就执行的工作总量而言,这种方式和反应器模式并没有区别。 我们只是简单地把职责在两个不同的角色之间进行转移。因为工作总量还是一样,所以并没有性能上的区别。相应的工作只是简单地让另一个角色来执行。下面的步骤列表证明了每个方式执行的工作总量:

标准/经典 的反应器:

步骤 1) 等待事件(反应器工作)

步骤 2) 分发“可读”事件给事件处理器(反应器工作)

步骤 3) 读取数据(事件处理器工作)

步骤 4) 处理数据(事件处理器工作)

建议的模拟前摄器

步骤 1) 等待事件(前摄器工作)

步骤 2) 读取数据(现在是前摄器工作)

步骤 3)分发“读完成”事件给事件处理器(前摄器工作)

步骤 4) 处理数据(事件处理器工作)


如果操作系统并没有提供异步IO API, 这个方案允许我们隐藏对于socket的操作并且导出一个完全的前摄方式的异步接口。这就使我们可以创建一个轻便的平台无关的并且拥有通用接口的解决方案。

猜你在找的React相关文章