理解Nodejs的Event Loop

Node的“event loop”主要是用来处理高输出量的。这很神奇,这也是为什么node可以在单线程的情况下同时处理很多的后台操作。本文就会集中讲述event loop是怎么运行的,这样你可以可以使用这个神奇的东西完成你自己的工作。

 

事件驱动的编程(event-driven programming)

要理解event loop首先需要了解的就是event driven programming(事件驱动的编程)。这个在1960年代就已经被人们所熟知。如今,event-driven proggramming被广泛的应用在UI处理中。javascript主要用在处理DOM中。

定义非常简单:event-driven programming就是程序的控制流程是由事件或者状态的改变决定的。主要的实现机制就是用一个中心控制台监听事件,并在事件发生的时候调用这个事件对应的回调函数(状态的改变也是一样)。很熟悉吧?这就是node的event loop的处理机制。

浏览器中的javascript开发中常常会遇到.on*()的方法,比如element.onclick(),用于连接用户操作和DOM。这个模式在一个单一元素可以发出多种可能的事件的时候工作的非常好。Node在EventEmitter中使用了这个模式,这个模式主要用在了server,socket和‘http’等模块中。这在一个实例需要发出多种类型的事件和状态的时候非常有用。

另一个普遍使用的模式是成功和失败。主流的实现方法有两种。第一个是发生错误的回调(原文:“error back”),就是在发生错误的时候把error作为第一个参数调用回调函数。另一种方法是在ES6中定义的,使用Promises。

‘fs’模块中大量使用了‘error back’。技术上来说,某些调用会发出其他的事件。比如,fs.readFile()。但是只在成功或者失败的时候才提醒用户。API这么设计主要是出于系统策略,不是技术的限制。

两一个很大的误解是事件发生机制本身是异步的。但是,这是不对的。下面的代码会表明这一点:

var EventEmitter = require('events').EventEmitter;
var util = require('util');

function MyEmitter() {
    EventEmitter.call(this);
}
util.inherits(MyEmitter, EventEmitter);

MyEmitter.prototype.doStuff = function() {
    console.log('before');
    this.emit('fire');
    console.log('after');
}

var me = new MyEmitter();
me.on('fire', function(){
    console.log('emit fired');
});

me.doStuff();

// Output:
// before
// emit fired
// after

EventEmitter看起来是异步的,因为他总是被用来发出异步操作完成的信号。但是,EventEmitter API是完全同步的。emit方法可能被异步调用,但是需要主要到全部的监听方法都是按照添加的顺序同步执行的。

 

总览

Node本身依赖于很多的库。其中之一就是libuv。这个库就是用来处理队列和异步的事件的。Node极大限度的使用了操作系统核心已经有的功能。申请写操作,保持连接以及更多地由系统处理的功能。比如,连接申请被系统排队,直到被Node处理。

你也许了解过Node有一个线程池,也会想知道“如果node把这些职责都推掉了,那还需要什么线程池?” 这是因为系统核心并不支持什么事都异步执行。比如,有时node需要锁定某个线程,这样event loop可以一直执行而不至于死锁。

这里有一个简化的图来解释event loop是怎么运行的。

有一些event loop的内部执行机制很难在图中给出:

  • 所有使用process.nexTick()指定的回调都会在event loop的某阶段的最后时刻,在进入下一个阶段前被执行(比如,timer)。这样有一个潜在的风险,如果process.nextTrick()方法有递归调用的话,整个event loop就被拖死了。
  • “待处理队列(pending callbacks)”就是未被其他阶段(phase)处理的回调队列(比如:给fs.write()传进去的回调)。

 

Event Emitter和Event Loop

为了简化和Event loop的互操作,所以有了EventEmitter。用EventEmitter可以很容易创建一个基于事件的API。下面我们就一些主要内容做讲解。

下面的代码展示了没有同步发出事件会造成用户错过事件的情况:

var EventEmitter = require('events').EventEmitter;
var util = require('util');

function MyThing() {
    EventEmitter.call(this);

    doFirstThing();
    this.emit('thing1');
}
util.inherites(MyThing, EventEmitter);

var mt = new MyThing();

mt.on('thing1', function(){
    // never going to happen.
});

以上代码的问题就在于‘thing1’永远不会被用户捕捉到,因为MyThing()必须在初始化完成之后才能监听事件。下面是一个简单地解决方案,不需要任何另外的闭包:

var EventEmitter = require('events').EventEmitter;
var util = require('util');

function MyThing() {
    EventEmitter.call(this);

    // doFirstThing();
    // this.emit('thing1');
    setImmediate(emitThing1, this);
}
util.inherits(MyThing, EventEmitter);

function emitThing1(self) {
    self.emit('thing1');
}

var mt = new MyThing();

mt.on('thing1', function(){
    // bravo
    console.log('bravo, thing1 captured.');
});

// bravo, thing1 captured.

下面的代码页可以运行,只不过消耗较多。

function MyThing() {
    EventEmitter.call(this);

    // doFirstThing();
    // this.emit('thing1');
    // setImmediate(emitThing1, this);
    setImmediate(this.emit.bind(this, 'thing1'));
}

另一种情况是触发错误。查出你的应用的问题非常麻烦,而且如果没有调用栈的话,简直无法排查。一个Error在异步执行的深处初始化的时候可能会导致调用栈丢失。解决这个问题最靠谱的两个办法就是同步emit事件,或者确保Error带了足够的相关信息。请看以下代码的演示:

MyThing.prototype.foo = function () {
    var er = doFirstThing();
    if (er) {
        // emit error asynchronously
        setImmediate(emitError, this, new Error('Bad stuff'));
        return;
    }

    // emit error synchronously
    var er = doSecondThing();
    if (er) {
        this.emit('error', 'More bad stuff');
        return;
    }
};

emit的错误应该立即被处理,以防程序继续执行。而且在构造函数中emit错误也不是个好主意。

 

最后

本文只介绍了event loop知识的一小部分。这些会在之后的文章中补足。但是,这是继续下去之前必备的基础。之后的文章会讲述event loop是怎么和系统的核心互相交互的。

 

欢迎加群互相学习,共同进步。QQ群:iOS: 58099570 | Android: 330987132 | Go:217696290 | Python:336880185 | 做人要厚道,转载请注明出处!

时间: 2024-11-03 14:08:59

理解Nodejs的Event Loop的相关文章

Node.js事件循环(Event Loop)和线程池详解_node.js

Node的"事件循环"(Event Loop)是它能够处理大并发.高吞吐量的核心.这是最神奇的地方,据此Node.js基本上可以理解成"单线程",同时还允许在后台处理任意的操作.这篇文章将阐明事件循环是如何工作的,你也可以感受到它的神奇. 事件驱动编程 理解事件循环,首先要理解事件驱动编程(Event Driven Programming).它出现在1960年.如今,事件驱动编程在UI编程中大量使用.JavaScript的一个主要用途是与DOM交互,所以使用基于事件

【朴灵评注】JavaScript 运行机制详解:再谈Event Loop

PS: 我先旁观下大师们的讨论,得多看书了~ 别人说的:"看了一下不觉得评注对到哪里去,只有吹毛求疵之感. 比如同步异步介绍,本来就无大错:比如node图里面的OS operation,推敲一下就可以猜到那是指同步操作(自然不走event loop了):至于watcher啥的,显然只是实现上的特色,即使用同一个queue实现也未尝不可" [原帖: http://www.ruanyifeng.com/blog/2014/10/event-loop.html 作者:阮一峰] 一年前,我写了

JavaScript 运行机制详解:再谈Event Loop

一年前,我写了一篇<什么是 Event Loop?>,谈了我对Event Loop的理解. 上个月,我偶然看到了Philip Roberts的演讲<Help, I'm stuck in an event-loop>.这才尴尬地发现,自己的理解是错的.我决定重写这个题目,详细.完整.正确地描述JavaScript引擎的内部运行机制.下面就是我的重写. 进入正文之前,插播一条消息.我的新书<ECMAScript 6入门>出版了(版权页,内页1,内页2),铜版纸全彩印刷,非常

[译] 理解 NodeJS 中基于事件驱动的架构

本文讲的是[译] 理解 NodeJS 中基于事件驱动的架构, 原文地址:Understanding Node.js Event-Driven Architecture 原文作者:Samer Buna 译文出自:掘金翻译计划 译者:刘德元 薛定谔的猫 校对者:bambooom zaraguo 理解 NodeJS 中基于事件驱动的架构 绝大部分 Node.js 对象,比如 HTTP 请求.响应以及"流",都使用了 eventEmitter 模块来支持监听和触发事件. 事件驱动最简单的形式是

什么是 Event Loop?

Event Loop 是一个很重要的概念,指的是计算机系统的一种运行机制. JavaScript语言就采用这种机制,来解决单线程运行带来的一些问题. 本文参考C. Aaron Cois的<Understanding The Node.js Event Loop>,解释什么是Event Loop,以及它与JavaScript语言的单线程模型有何关系. 想要理解Event Loop,就要从程序的运行模式讲起.运行以后的程序叫做"进程"(process),一般情况下,一个进程一次

JavaScript运行机制之事件循环(Event Loop)详解_javascript技巧

一.为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊. JavaScript的单线程,与它的用途有关.作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM.这决定了它只能是单线程,否则会带来很复杂的同步问题.比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线

eclipse的Unhandled event loop exception PermGen space问题

原来一直用eclipse3.5,最近尝试升级到3.7和4.2,但不管是3.7还是4.2项目编译过程中总提示"Unhandled event loop exception PermGen space"要求退出workspace,与原来3.5版本比较了一下eclipse.ini参数设置发现没有差别,怀疑是公司自己开发的插件问题,马上删除自己的插件,但还是遇到同样问题,郁闷只好去google求助. 搜索到的第一个解决方案是设置PermSize和MaxPermSize参数,避免耗光永久保存区内

JavaScript Event Loop机制详解与Vue.js中nextTick的实践应用

本文依次介绍了函数调用栈.MacroTask 与 MicroTask 执行顺序.浅析 Vue.js 中 nextTick 实现等内容;本文中引用的参考资料统一声明在 JavaScript 学习与实践资料索引. 1. 事件循环机制详解与实践应用 JavaScript 是典型的单线程单并发语言,即表示在同一时间片内其只能执行单个任务或者部分代码片.换言之,我们可以认为某个同域浏览器上下中 JavaScript 主线程拥有一个函数调用栈以及一个任务队列(参考 whatwg 规范);主线程会依次执行代码

java-myEclipse生成uml类图失败,报Unhandled event loop exception

问题描述 myEclipse生成uml类图失败,报Unhandled event loop exception 在用单个包或类可以正常生成uml类图,在用测试的小工程也可行,但是用在公司的工程上就失败了,不知道是不是工程太大,下面是log里的记录: !ENTRY org.eclipse.ui 4 0 2015-05-07 16:17:57.118 !MESSAGE Unhandled event loop exception !STACK 0 org.eclipse.swt.SWTError: