[For the record] 尽管删掉了被黑的Wordpress博客,还是觉得应该把这篇留着。
Stackoverflow原帖:What is the different between even driven model and reactor pattern
[提问 by Howard]
在维基百科(英文)Reactor Pattern的条目中,有如下描述:
The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs.
反应器模式是一种事件处理模式,用于处理从一个或多个来源同时分发到服务处理器的服务请求。
文中举了一些例子,例如node.js,twisted,eventmachine。但是在我的理解中,上面这些都是流行的“事件驱动框架”——于是它们也就是“反应器模式”的框架?如何区分这两者?或者说,它们是相同的么?
[回答 by Jean-Paul Calderone]
“反应器模式”比“事件驱动编程”要更加具体——它是在使用事件驱动方法编程时的一种具体的实现方式。但是在平常的交谈中,这个词的精确意思并不常用。所以,你应该小心使用这个词,确保你的听众能正确理解你的意思;同样,当你遇到他人使用这个词的时候,也要留意你的理解是否与他人的本意有所偏差。
看待反应器模式的一种方式是将其与“非阻塞操作”密切联系起来。当某个操作可以不被阻塞地完成时,反应器就会发出通知。例如,select(2)可以实现反应器模式,以便使用标准的BSD Socket API(recv(2),send(2)等等)来读写socket。例如,当一个socket在系统内核中的接收缓存中有字节可读的时候,也就是说,你能从中读取到数据的时候,select就会立刻告诉你。
在考虑这些概念的时候,另一个你可能会感兴趣的模式就是“前摄器模式”(Proactor Pattern)。与反应器模式正好相反,前摄器模式不考虑操作是否能够立刻完成,而是直接开始操作,使其异步执行,并等它完成之后再安排分发事件通知。
Windows I/O Completion Ports(IOCP)就是前摄器模式的一个例子。当使用IOCP在一个socket上进行发送操作的时候,该操作会立刻开始,无论这个socket的内核发送缓存是否有空间来容纳数据;当WSASend调用完成的时候,发送操作才会继续(在另一个线程里,可能是在一个系统线程里)。当发送操作真的完成了(即要发送的所有字节全都已经被复制到发送缓存之后),提供给WSASend调用的回调函数就会被执行(在应用程序的新线程里)。
这种发起操作并在完成时得到通知的做法,正是异步操作思想的核心。而非阻塞操作,则是只有当操作可以立刻完成的时候才发起操作,否则将一直等待。
这两种方式都可以用来进行事件驱动的开发。使用反应器模式的时候,程序将等待例如“某个socket可读了”的事件,当事件发生时,就去读取;而使用前摄器模式的时候,程序则直接发起读取的操作,并等待读取结束事件的发生(然后对结果进行处理)。
严格地说,Twisted误用了Reactor这个词。The Twisted reactor基于select(2)并以非阻塞IO的方式实现,这几乎就是一个反应器;但是,它提供给使用者的接口是异步的,这一部分更像是前摄器。Twisted还有一个基于IOCP的反应器,这个反应器为应用程序提供的也是异步的API,并且使用了前摄器式的IOCP API。它是一个混血,而且在不同平台上有着不同的实现细节,因此,用Reactor和Proactor来描述都不准确。不过,由于twisted.internet.reactor里提供的API基本全都是异步API,而非“非阻塞”API,用Procator来称呼Twisted应该会更加合适。
[回答者追加评论摘要]
select(2)是非常底层的,而且很难使用。当有很多个事件源的时候,它还不够高效。现代的系统提供了功能相同但更加高效的API,如/dev/poll,epoll等。Twisted及类似的库的好处在于,它们提供了对各种不同实现方式的抽象,使得应用程序可以使用一套统一的API,同时仍然能享受到底层系统提供的高效API的支持。
select(2)和epoll(4)的比较,可以参考这篇Q&A:Caveats of select/poll vs. epoll reactors in Twisted