理解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是怎么和系统的核心互相交互的。
理解Nodejs的Event Loop的更多相关文章
- NodeJs的Event Loop
我们之前谈过浏览器的Event Loop:https://www.cnblogs.com/amiezhang/p/11349450.html 简单来说,就是每执行一个宏任务,就去执行微任务队列,直到清 ...
- 理解Javascript的Event Loop
一.单线程 js作为浏览器脚本语言,他的主要用途是与用户交互,以及操作DOM,这决定了它只能是单线程,为什么呢?因为假如js同时有两个线程,一个线程是在DOM上增加内容,另一个线程是删除这个节点,那么 ...
- 【Node.js】Event Loop执行顺序详解
本文基于node 0.10.22版本 关于EventLoop是什么,请看阮老师写的什么是EventLoop 本文讲述的是EventLoop中的执行顺序(着重讲setImmediate, setTime ...
- 关于event loop
之前写了篇文章 JS运行机制,里面对event loop简单的说明,面试时又遇到了关于该知识点的题目(主要是process.nextTick和setImmediate的执行顺序不太知道,查了之后才知道 ...
- [转]Event loop——浏览器和Node区别
最近对Event loop比较感兴趣,所以了解了一下.但是发现整个Event loop尽管有很多篇文章,但是没有一篇可以看完就对它所有内容都了解的文章.大部分的文章都只阐述了浏览器或者Node二者之一 ...
- Event Loop事件循环,GET!
JS中比较让人头疼的问题之一要算异步事件了,比如我们经常要等后台返回数据后进行dom操作,又比如我们要设置一个定时器完成特定的要求.在这些同步与异步事件里,异步事件肯定是在同步事件之后的,但是异步事件 ...
- [NodeJs系列][译]理解NodeJs中的Event Loop、Timers以及process.nextTick()
译者注: 为什么要翻译?其实在翻译这篇文章前,笔者有Google了一下中文翻译,看的不是很明白,所以才有自己翻译的打算,当然能力有限,文中或有错漏,欢迎指正. 文末会有几个小问题,大家不妨一起思考一下 ...
- JavaScript 运行机制详解:深入理解Event Loop
Philip Roberts的演讲<Help, I'm stuck in an event-loop>,详细.完整.正确地描述JavaScript引擎的内部运行机制. 一.为什么JavaS ...
- 对Node.JS的事件轮询(Event Loop)的理解
title: Node.JS的事件轮询(event loop)的理解 categories: 理解 tags: Node JS 机制 当我们知道I/O操作和创建新线程的开销是巨大的! 网站延迟的开销 ...
随机推荐
- Mybatis知识(5)
1. #{} 与${}的区别 #{}解释为 JDBC prepared statement 的一个参数标记,而${}解释为字符串替换.比如:我们不能在表名的位置使用参数标记,也不能在列名的位置使用参数 ...
- smartos介绍
https://wiki.smartos.org A Little History 2005年,Sun Microsystems开源了其著名的Unix操作系统Solaris,最终被发布为一个名为Ope ...
- 大型运输行业实战_day10_1_自定义事务管理类
1.创建事务管理类 TransactionManager.java package com.day02.sation.transaction; import com.day02.sation.uti ...
- sqlserver批量导出存储过程、函数、视图
select text from syscomments s1 join sysobjects s2 on s1.id=s2.id where xtype = 'V' xtype V 视图 P ...
- JS中如何处理多个ajax并发请求?
js中的多并发处理. 通常 为了减少页面加载时间,先把核心内容显示处理,页面加载完成后再发送ajax请求获取其他数据 这时就可能产生多个ajax请求,为了用户体验,最好是发送并行请求,这就产生了并发问 ...
- hbase概念
1. 概述(扯淡~) HBase是一帮家伙看了Google发布的一片名为“BigTable”的论文以后,犹如醍醐灌顶,进而“山寨”出来的一套系统. 由此可见: 1. 几乎所有的HBase中的理念,都可 ...
- ECMAScript5新特性之Object.isExtensible、Object.preventExtensions
阻止对象扩展后: 1 不能添加属性. 2 可以修改属性的值. 3 可以删除属性. 4 可以修改属性描述符. var fruit = { name : '苹果', desc : '红富士' }; // ...
- centos自定义安装pip3
题记 在之前的文章centos云服务器安装Python3记录 记录了怎么自定义安装 Python3 ,在后边测试pip3的时候发现了个问题: pip --version terminal 打印: pi ...
- 矩形最小路径和 · Minimum Path Sum
[抄题]: 给定一个只含非负整数的m*n网格,找到一条从左上角到右下角的可以使数字和最小的路径. [思维问题]: [一句话思路]: 和数字三角形基本相同 [输入量]:空: 正常情况:特大:特小:程序里 ...
- VS代码页到设计页传值
代码页 public string titleZRclass = ""; 根据条件设置其值 设计页 <%=titleZRclass %>