node.js 事件驱动

在传统编程中,i/o操作和本地函数调用的处理方式相同:处理过程需要一直等待直到某个操作结束才能继续下去,这种基于i/o操作的阻塞式编程模型继承自早期的分时系统,在这类系统中,每一个进程都对应着一个用户,这样做的目的是使得用户之间相互隔离。并且在这类系统中,用户在决定下一个操作前,必须先结束一个操作。显然,这种“单用户,单进程”的模型不具备较好的伸缩性,当任务的数量达到一定的数量之后性能开始急剧下降

除了上述编程模型之外,多线程编程模型是另外一个选择。线程可以看作是一种轻量级的进程,它与同一进程里的其他线程共享内存。线程作为早期模型的拓展而被创建,以适应若干并发线程的执行。当一个线程等待i/o操作时,另一个线程就可以接管CPU,等待线程将i/o操作结束时被唤醒。多线程编程的缺点是什么?程序员并不知道在给定时刻究竟是哪些线程在执行,所以他们必须仔细处理对共享内存状态的并发访问,使用诸如线程锁和信号量这样的同步原语协调对某些数据结构的访问,强制他们对线程执行调度的各种途径进行预测以避免问题的发生。假如应用程序非常依赖于线程间的共享状态,那么这种编程方式容易导致随机发生的危险错误,并且这些错误很难查找。

如果不让操作系统进行线程调度,那么程序员的另一个选择是协同多线程,在这种模式下,程序员负责显式地让CPU为其他进程的执行分配时间,因为是人为负责线程调度,所以可以放宽同步需求,但是基于和普通多线程同样的原因,这种方法也很复杂并且容易出错

说了这么多,其实事件驱动编程是指程序的执行流程取决于事件的编程风格,事件由事件处理程序或者事件回调函数进行处理。当某些重要的事件发生时——例如数据库查询结果或者用户单击了某个按钮,就会调用事件回调函数。以下面的代码为例,看看在典型的阻塞式i/o模型里,如何实现对数据库的查询:

    var result=query('select * from posts where id=1');
    do_something_with(result);
  • 1
  • 2

显然,上述查询过程需要当前线程或者进程一直等待下去,直到数据库层完成对数据的查询处理为止,而在事件驱动系统中,上述查询则以如下方式进行:

    query_finished=function(result){
        do_something_with(result);
    }
    query('select * from posts where id=1',query_finished);
  • 1
  • 2
  • 3
  • 4

简单解释一下:此次首先定义了在查询完成以后将会发生的处理过程,并且将这个过程存储在一个名为query_finished 的函数当中,然后讲函数名作为查询的一个参数。这样当查询完成后将会调用query_finished 函数,而不仅仅只是返回查询结果。这种编程风格的好处就是:当前进程在处理i/o操作时不会发生阻塞,因此多个i/o操作可以并行进行,当每个操作结束时将会分别调用其对应的回调函数

再补充一个概念:事件循环,事件循环和事件驱动相伴相生,包括事件监测和事件触发处理,在每一轮循环中它必须检测发生了什么事件,调用了哪个回调函数。事件循环只是在一个进程中运行的单个线程,这意味着当事件发生时,可以不用中断就运行时间处理程序,这样做的好处有两个: 
1. 在任一给定时刻,最多运行一个事件处理程序。 
2. 事件处理程序可以不间断地运行直到结束。 
这使得程序员能够放宽同步要求,并且不必担心执行并发线程会改变共享内存的状态

猜你喜欢

转载自blog.csdn.net/mrfang1413/article/details/82420283