Node.js 事件驱动编程
说到事件驱动编程,得要先介绍下传统编程的风格。
1.阻塞式编程模型:
特点是“单用户,单进程”,为了使用户之间相互隔离,每一个进程都对应着一个用户,用户在决定下一个操作之前,必须要先结束一个操作。
2.多线程编程模型:
线程可以看成一种轻量级的进程,它与同一进程内的其他线程共享内存。线程可以中断,之后又可以恢复执行。当一个线程等待I/O操作时,另一个进程就可以接管CPU,等待线程将在I/O操作结束时被唤醒。
3.事件驱动编程风格:
3.1 概念
以定义当感兴趣事件发生时有系统调用的函数来取代应用返回值的编程风格被称为事件驱动编程或者异步编程。
举个例子:
(1)
在美国去看医生,需要填写大量表格,比如保险、个人信息之类,传统的基于线程的系统(thread-based system),接待员叫到你,你需要在前台填写完成这些表格,你站着填单,而接待员坐着看你填单。你让接待员没办法接待下一个客户,除非完成你的业务。想让这个系统能运行的快一些,只有多加几个接待员,人力成本需要增加不少。基于事件的系统(event-based system)中,当你到窗口发现需要填写一些额外的表格而不仅仅是挂个号,接待员把表格和笔给你,告诉你可以找个座位填写,填完了以后再回去找他。你回去坐着填表,而接待员开始接待下一个客户。你没有阻塞接待员的服务。你填完表格,返回队伍中,等接待员接待完现在的客户,你把表格递给他。如果有什么问题或者需要填写额外的表格,他给你一份新的,然后重复这个过程。这个系统已经非常高效了,几乎大部分医生都是这么做的。如果等待的人太多,可以加入额外的接待员进行服务,但是肯定要比基于线程模式的少得多。
(2)
以下面的代码为例,看看在典型的阻塞式i/o模型里,如何实现对数据库的查询:
var result=query('select * from posts where id=1'); do_something_with(result);
显然,上述查询过程需要当前线程或者进程一直等待下去,直到数据库层完成对数据的查询处理为止,而在事件驱动系统中,上述查询则以如下方式进行:
query_finished=function(result){ do_something_with(result); } query('select * from posts where id=1',query_finished);
这样当查询完成后将会调用query_finished 函数,而不仅仅只是返回查询结果。这种编程风格的好处就是:当前进程在处理i/o操作时不会发生阻塞,因此多个i/o操作可以并行进行,当每个操作结束时将会分别调用其对应的回调函数。
3.2 Nodejs在服务器端的表现
传统的web server多为基于线程模型。你启动Apache或者什么server,它开始等待接受连接。当收到一个连接,server保持连接连通直到页面或者什么事务请求完成。如果他需要花几微妙时间去读取磁盘或者访问数据库,web server就阻塞了IO操作(这也被称之为阻塞式IO).想提高这样的web server的性能就只有启动更多的server实例。相反的,Node.Js使用事件驱动模型,当web server接收到请求,就把它关闭然后进行处理,然后去服务下一个web请求。当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户。这个模型非常高效可扩展性非常强,因为webserver一直接受请求而不等待任何读写操作。(这也被称之为非阻塞式IO或者事件驱动IO)
简要概括:web server 监听请求后,异步执行io操作的同时,web server主程继续监听请求,因此不会造成IO阻塞。