Node.js事件循环(Event Loop)和线程池详解_node.js
Node的“事件循环”(Event Loop)是它能够处理大并发、高吞吐量的核心。这是最神奇的地方,据此Node.js基本上可以理解成“单线程”,同时还允许在后台处理任意的操作。这篇文章将阐明事件循环是如何工作的,你也可以感受到它的神奇。
事件驱动编程
理解事件循环,首先要理解事件驱动编程(Event Driven Programming)。它出现在1960年。如今,事件驱动编程在UI编程中大量使用。JavaScript的一个主要用途是与DOM交互,所以使用基于事件的API是很自然的。
简单地定义:事件驱动编程通过事件或状态的变化来进行应用程序的流程控制。一般通过事件监听实现,一旦事件被检测到(即状态改变)则调用相应的回调函数。听起来很熟悉?其实这就是Node.js事件循环的基本工作原理。
如果你熟悉客户端JavaScript的开发,想一想那些.on*()方法,如element.onclick(),他们用来与DOM元素相结合,传递用户交互。这个工作模式允许在单个实例上触发多个事件。Node.js通过EventEmitter(事件发生器)触发这种模式,如在服务器端的Socket和 “http”模块中。可以从一个单一实例触发一种或一种以上的状态改变。
另一种常见的模式是表达成功succeed和失败fail。现在一般有两种常见的实现方式。首先是将“Error异常”传入回调,一般作为第一个参数传递给回调函数。第二种即使用Promises设计模式,已经加入了ES6。注* Promise模式采用类似jQuery的函数链式书写方式,以避免深层次的回调函数嵌套,如:
$.getJSON('/getUser').done(successHandler).fail(failHandler)
“fs”(filesystem)模块大多采用往回调中传入异常的风格。在技术上触发某些调用,例如fs.readFile()附加事件,但该API只是为了提醒用户,用来表达操作成功或失败。选择这样的API是出于架构的考虑,而非技术的限制。
一个常见的误解是,事件发生器(event emitters)在触发事件时也是天生异步的,但这是不正确的。下面是一个简单的代码片段,以证明这一点。
function MyEmitter() {
EventEmitter.call(this);
}
util.inherits(MyEmitter, EventEmitter);
MyEmitter.prototype.doStuff = function doStuff() {
console.log('before')
emitter.emit('fire')
console.log('after')}
};
var me = new MyEmitter();
me.on('fire', function() {
console.log('emit fired');
});
me.doStuff();
// 输出:
// before
// emit fired
// after
注* 如果 emitter.emit 是异步的,则输出应该为
// before
// after
// emit fired
EventEmitter经常表现地很异步,因为它经常用于通知需要异步完成的操作,但EventEmitter API本身是完全同步的。监听函数内部可以按异步执行,但请注意,所有的监听函数将按被添加的顺序同步执行。
机制概述和线程池
Node本身依赖多个库。其中之一是libuv,神奇的处理异步事件队列和执行的库。
Node利用尽可能多的利用操作系统内核实现现有的功能。像生成响应请求(request),转发连接(connections)并委托给系统处理。例如,传入的连接通过操作系统进行队列管理,直到它们可以由Node处理。
您可能听说过,Node有一个线程池,你可能会疑惑:“如果Node会按次序处理任务,为什么还需要一个线程池?”这是因为在内核中,不是所有任务都是按异步执行的。在这种情况下,Node.JS必须能在操作时将线程锁定一段时间,以便它可以继续执行事件循环而不会被阻塞。
下面是一个简单的示例图,来表示他内部的运行机制:
┌───────────────────────┐
╭──►│ timers │
│ └───────────┬───────────┘
│ ┌───────────┴───────────┐
│ │ pending callbacks │
│ └───────────┬───────────┘ ┌──────────────┐
│ ┌───────────┴───────────┐ │ incoming: │
│ │ poll │◄──┤ connections, │
│ └───────────┬───────────┘ │ data, etc. │
│ ┌───────────┴───────────┐ └──────────────┘
╰───┤ setImmediate │
└───────────────────────┘
关于事件循环的内部运行机制,有一些理解困难的地方:
所有回调都会经由process.nextTick(),在事件循环(例如,定时器)一个阶段的结束并转换到下一阶段之前预设定。这就会避免潜在的递归调用process.nextTick(),而造成的无限循环。
“Pending callbacks(待回调)”,是回调队列中不会被任何其他事件循环周期处理(例如,传递给fs.write)的回调。
Event Emitter 和 Event Loop
通过创建EventEmitter,可简化与事件循环的交互。它是一个通用的封装,可以让你更容易地创建基于事件的API。关于这两者如何互动往往让开发者感到混乱。
下面的例子表明,忘记了事件是同步触发的,可能导致事件被错过。
// v0.10以后,不再需要require('events').EventEmitter
var EventEmitter = require('events');
var util = require('util');
function MyThing() {
EventEmitter.call(this);
doFirstThing();
this.emit('thing1');
}
util.inherits(MyThing, EventEmitter);
var mt = new MyThing();
mt.on('thing1', function onThing1() {
// 抱歉,这个事件永远不会发生
});
上面的'thing1'事件,永远不会被MyThing()捕获,因为MyThing()必须在实例化后才能侦听事件。下面的是一个简单的解决方法,不必添加任何额外的闭包:
var EventEmitter = require('events');
var util = require('util');
function MyThing() {
EventEmitter.call(this);
doFirstThing();
setImmediate(emitThing1, this);
}
util.inherits(MyThing, EventEmitter);
function emitThing1(self) {
self.emit('thing1');
}
var mt = new MyThing();
mt.on('thing1', function onThing1() {
// 执行了
});
下面的方案也可以工作,不过要损失一些性能:
function MyThing() {
EventEmitter.call(this);
doFirstThing();
// 使用 Function#bind() 会损失性能
setImmediate(this.emit.bind(this, 'thing1'));
}
util.inherits(MyThing, EventEmitter);
另一个问题是触发Error(异常)。找出您应用程序中的问题已经很难了,但没了调用堆栈(注* e.stack),则几乎不可能调试。当Error被远端的异步请求调用堆栈将丢失。有两个可行的解决方案:同步触发或确保Error跟其他重要信息一起传入。下面的例子演示了这两种解决方案:
MyThing.prototype.foo = function foo() {
// 这个 error 会被异步触发
var er = doFirstThing();
if (er) {
// 在触发时,需要创建一个新的保留现场调用堆栈信息的error
setImmediate(emitError, this, new Error('Bad stuff'));
return;
}
// 触发error,马上处理(同步)
var er = doSecondThing();
if (er) {
this.emit('error', 'More bad stuff');
return;
}
}
审时度势。当error被触发时,是有可能被立即处理的。或者,它可能是一些琐碎的,可以很容易处理,或在以后再处理的异常。此外通过一个构造函数,传递Error也不是一个好主意,因为构造出来的对象实例很有可能是不完整的。刚才直接抛出Error的情况是个例外。
结束语
这篇文章比较浅显地探讨了有关事件循环的内部运作机制和技术细节。都是经过深思熟虑的。另一篇文章会讨论事件循环与系统内核的交互,并展现NodeJS异步运行的魔力。

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

热门话题

基于无阻塞、事件驱动建立的Node服务,具有内存消耗低的优点,非常适合处理海量的网络请求。在海量请求的前提下,就需要考虑“内存控制”的相关问题了。 1. V8的垃圾回收机制与内存限制 Js由垃圾回收机

如何在Java7中使用线程池来实现任务的循环调度引言:在开发Java应用程序时,使用线程池可以提高任务的执行效率和资源利用率。在Java7中,使用线程池可以很方便地实现任务的循环调度。本文将介绍如何在Java7中使用线程池来实现任务的循环调度,并附上相应的代码示例。一、概述:线程池是一种多线程处理结构,它可以重复使用固定数量的线程,从而避免频繁地创建和

最开始的时候 JS 只在浏览器端运行,对于 Unicode 编码的字符串容易处理,但是对于二进制和非 Unicode 编码的字符串处理困难。并且二进制是计算机最底层的数据格式,视频/音频/程序/网络包

Linux是一款优秀的操作系统,广泛应用于服务器系统中。在使用Linux系统的过程中,服务器负载问题是一种常见的现象。服务器负载是指服务器的系统资源无法满足当前的请求,导致系统负载过高,从而影响服务器性能。本文将介绍Linux系统下常见的服务器负载问题及其解决方法。一、CPU负载过高当服务器的CPU负载过高时,会导致系统响应变慢、请求处理时间变长等问题。当C

PHP8.0中的事件处理库:Event随着互联网的不断发展,PHP作为一门流行的后台编程语言,被广泛应用于各种Web应用程序的开发中。在这个过程中,事件驱动机制成为了非常重要的一环。PHP8.0中的事件处理库Event将为我们提供一个更加高效和灵活的事件处理方式。什么是事件处理在Web应用程序的开发中,事件处理是一个非常重要的概念。事件可以是任何一种用户行

1.为什么要使用异步编程?传统编程使用阻塞式I/O,这意味着程序会等待某个操作完成,然后才能继续执行。这对于处理单个任务来说可能很有效,但对于处理大量任务时,可能会导致程序变慢。异步编程则打破了传统阻塞式I/O的限制,它使用非阻塞式I/O,这意味着程序可以将任务分发到不同的线程或事件循环中执行,而无需等待任务完成。这允许程序同时处理多个任务,提高程序的性能和效率。2.python异步编程的基础Python异步编程的基础是协程和事件循环。协程是允许函数在暂停和恢复之间切换的函数。事件循环则负责调度

异步编程,英文AsynchronousProgramming,是指程序中的某些任务可以并发地执行,而无需等待其他任务完成,从而提高程序的整体运行效率。在python中,asyncio模块是实现异步编程的主要工具,它提供了协程、事件循环和其他异步编程所需的组件。协程:协程(Coroutine)是一种特殊的函数,它可以被暂停然后恢复执行,就像线程一样,但协程比线程更轻量级,内存消耗更低。协程由async关键字声明,并在await关键字处暂停执行。事件循环:事件循环(EventLoop)是异步编程中的

JS-Torch简介JS-Torch是一种深度学习JavaScript库,其语法与PyTorch非常相似。它包含一个功能齐全的张量对象(可与跟踪梯度),深度学习层和函数,以及一个自动微分引擎。JS-Torch适用于在JavaScript中进行深度学习研究,并提供了许多方便的工具和函数来加速深度学习开发。图片PyTorch是一个开源的深度学习框架,由Meta的研究团队开发和维护。它提供了丰富的工具和库,用于构建和训练神经网络模型。PyTorch的设计理念是简单和灵活,易于使用,它的动态计算图特性使
