事件驱动编程详解Node.js

在传统的编程模型,我 / O操作像一个普通的本地函数调用的程序:前挡功能完成,无法继续运行。果酱我 / O起源于更早的时间切片模型,这个模型的每一个过程作为一个独立的人,目的是分离的每个区,每个人都在同一时间只能做一件事,必须等前面的工作做决定接下来做什么。但这类用户,它被广泛应用于计算机网络和互联网,有一个过程模型的可扩展性差。在管理多个进程,它将耗费大量的内存,和上下文切换也将占用大量的资源,这是一个很大的负担的操作系统。随着过程数的增加,系统的性能将大大降低。

多线程是一个另类,线程是轻量级的进程,它将在同一个进程共享内存和其他的线程,它更像是传统模式的延伸,对多线程并行执行,当一个线程正在等待我/ O操作,其他线程可以接管CPU,当我 / O操作完成前等待的线程将被唤醒。换句话说,一个正在运行的线程可以被打断,然后恢复。此外,在某些系统中,线程可以并行运行多核CPU不同的内核。

程序员不知道该线程将运行在一个特定的时间,他们必须非常小心的共享内存并行,所以我们必须使用一些同步原语访问数据结构的同步,如锁和信号量的使用,以迫使线程的具体行为和计划的执行这些应用程序在很大程度上依赖于共享状态的线程之间可以有很大的随机性和难找到一些奇怪的问题。

另一种方法是通过合作,使用多线程,你负责CPU类型明显释放,和CPU时间给其他线程,因为由你亲自来控制线程的执行计划,从而减少了同步的要求,同时也提高了程序的复杂度,一个很好的机会,但没有避免多线程的问题。

什么是事件驱动编程

事件驱动编程(事件驱动的编程)是一种编程风格,通过事件来确定程序的执行过程中,由事件处理器事件(事件处理程序)(回调)或事件的回调函数处理事件回调当特定事件发生时调用的函数返回数据库,如查询结果或用户单击一个按钮。

现在回想起来,在传统的阻塞I/O编程模式中,数据库查询可能是这样的:

复制代码代码如下所示:

结果:查询('select *岗位在ID = 1);

do_something_with(结果);



上面的查询函数将保持当前线程或进程等待,直到基础数据库完成查询操作并返回为止。

在事件驱动的模型中,这个查询将转向:

复制代码代码如下所示:

query_finished =函数(结果){

do_something_with(结果);

}

查询('select *帖子id是1,query_finished);



首先,你定义了一个函数调用query_finished,其中包含你想要做什么,当查询完成。功能可以作为一个参数传递给查询功能,并执行查询时,该query_finished调用而不是只返回查询结果。

当您对该事件感兴趣时,您将调用所定义的函数,而不是简单地返回结果值。这种编程模型称为事件驱动编程或异步编程,这是节点最明显的特性。这个编程模型意味着在执行I/O操作时,当前进程不会被阻塞,因此可以并行执行多个I/O操作。当操作完成时,相应的回调函数将被调用。

事件驱动编程依赖事件循环。事件循环基本上是一个由事件检测和事件处理器触发两个功能组成的结构,在每一个循环中,事件循环机制需要检测发生的事件,当事件发生时,它会找到相应的回调函数并调用它。

事件循环只是进程中运行的一个线程,当事件发生时,事件处理程序可以单独运行,不会中断,也就是说:

1。在特定的时间,有一个事件回调函数最多运行。

2。运行时不会中断任何事件处理程序。

有了这个,开发人员就不再为线程同步和共享内存的并发修改而头疼了。

众所周知的秘密:

很久以前,系统编程界的人们知道,事件驱动编程是创建高并发服务的最佳方法,因为它不保存很多上下文,因此节省了大量内存,而且没有太多上下文切换,节省了大量执行时间。

慢慢地,这种思想已经渗透到其他平台和社区,并出现了一些著名的事件循环,如红宝石的活动机,Perl和Python的扭曲anyevnet。除此之外,还有许多其他实现和语言。

使用这些框架来开发,您需要学习框架和特定类库的特定知识。例如,使用事件机时,为了享受非阻塞的好处,你必须避免使用同步的类库,只有使用事件机异步类库。如果你使用任何阻塞的类库(如大多数Ruby标准库),你的服务器将失去最好的可扩展性,因为事件循环将继续被封锁,现在它会妨碍我/ O事件处理。

节点最初是作为一个非阻塞I/O服务器平台设计的,所以一般来说,您应该期望运行在其上的所有代码都是非阻塞的,因为Javascript非常小,而且它不强制任何I/O模型(因为它没有标准的I/O类库)。因此,节点建立在一个非常纯净的环境中,没有任何历史问题。

节点和Javascript如何简化异步应用程序

Ryan Dahl,节点的作者,最初是用C来开发这个项目,但发现上下文保持函数调用太复杂,导致高的编码复杂度。然后他转向Lua,但已经有一些阻止我lua I/O类库。阻塞和非阻塞的混合可能会混淆开发商和防止很多人从建立可扩展的应用程序,所以Lua也抛弃了达尔。最后,他转向Javascript闭包的函数,在Javascript闭包和第一级对象,使Javascript事件驱动的编程非常适合。Javascript的魔法是Node为什么如此受欢迎的主要原因。

什么是壁橱

闭包可以理解为一种特殊的功能,但它可以继承和访问自己的定义范围的变量,当你添加一个回调函数作为参数传递给另一个函数,它后来被称为魔术,回调函数之后调用,它能记住自己的定义语境与父上下文中的变量,还可以访问它们。这个强大的功能是节点成功的核心。

下面的示例将展示Javascript闭包如何在Web浏览器中工作。如果您想监视一个按钮的单个事件,您可以这样做:

复制代码代码如下所示:

无功clickcount = 0;

document.getelementbyid('mybutton)。Onclick =函数(){

clickcount = 1;

警报(点击+ clickcount +时代。);

};



jQuery使用时是这样的。

复制代码代码如下所示:

无功clickcount = 0;

$(按钮#色)。Click(function(){)

clickedcount + +;

警报('clicked + clickcount +倍。);

});



在Javascript中,函数是对象,第一类,你可以把函数作为参数等功能。上面的两个例子,分配给另一个函数的原函数,传递函数的另一个函数作为参数,单击事件处理函数(回调函数)可以访问每个变量的代码块中的函数定义,在这种情况下,它可以在关闭的clickcount变量定义父。

在全球范围内的clickcount变量(最外层的范围,它使Javascript)的用户数量,点击按钮,通常存储在变量的全局作用域是一个坏习惯,因为它很容易与其他冲突的代码,你应该把变量在使用局部范围。大多数的时间,只是包装代码与一个功能相当于创建一个闭包,这使得它很容易避免污染全球环境。

复制代码代码如下所示:

(函数(){())

无功clickcount = 0;

$(按钮#色)。Click(function(){)

clickcount + +;

警报('clicked + clickcount +倍。);

});

}();





注意:上述代码的第七行在调用它后立即定义函数,这是Javascript中的一种常见设计模式:通过创建函数创建一个新的作用域。

闭包如何帮助异步编程

在事件驱动编程模型中,我们首先编写将在事件发生后运行的代码,然后将这些代码放入函数中,最后将函数作为参数传递给调用者,稍后由调用者函数调用该参数。

在Javascript中,函数不是孤立的定义。它还记住已声明的范围的上下文。这种机制允许Javascript函数访问函数定义和父上下文上下文中的所有变量。

当你通过一个回调函数作为参数调用函数,函数是在稍后的时间打电话。即使回调函数的范围结束,它仍然可以访问所有的变量在完成范围和其母的范围时,回调函数被调用。像最后一个例子中,回调函数在单击调用jQuery(),但它仍然可以访问clickcount变量。

封闭的魔力展现在前面。将状态变量传递给函数可以在没有维护状态的情况下启用事件驱动编程。Javascript的关闭机制将帮助您维护它们。

总结

事件驱动的编程是一种编程模型,确定程序的执行流程通过事件触发。程序员登记回调函数(通常称为事件处理程序),他们所感兴趣的事件,然后系统调用注册事件处理程序时,事件发生。这种编程模型具有许多优点,传统的模块化的编程模型不具备。在实现相似特性之前,必须使用多进程/多线程。

Javascript是一种强大的语言,因为它的第一个类型对象的函数和闭包特性使它适合事件驱动编程。