Node.js之深刻理解特性

前言

当咱们在接触学习Node.js的时候,估计咱们看的最多关于Node.js特性的词是单线程异步无阻塞事件驱动。本文经过这几个特征词汇深刻聊聊Node.js的特性。javascript


单线程

咱们都知道Node.js的runtime是v8,v8在设计之初是Chrome使用在浏览器对JavaScript语言的解析运行引擎,其最大的特色是单线程,而在Node.js对v8的沿用也是针对这一很是重要的特色。什么是单线程?简单来讲就是一个进程中只有一个线程,程序顺序执行,前面执行完成才会执行后面的程序。来看个Node.js对http服务的模型:html

Node.js单线程

Node.js的单线程指的是主线程是“单线程”,由主要线程去按照编码顺序一步步执行程序代码,假如遇到同步代码阻塞,主线程被占用,后续的程序代码执行就会被卡住。实践一个测试代码:java

var http = require('http');

function sleep(time) {
    var _exit = Date.now() + time * 1000;
    while( Date.now() < _exit ) {}
    return ;
}

var server = http.createServer(function(req, res){
    sleep(10);
    res.end('server sleep 10s');
});

server.listen(8080);

下面为代码块的堆栈图:c++

堆栈图

JavaScript是解析性语言,代码按照编码顺序一行一行被压进stack里面执行,执行完成后移除而后继续压下一行代码块进去执行。上面代码块的堆栈图,当主线程接受了request后,程序被压进同步执行的sleep执行块(咱们假设这里就是程序的业务处理),若是在这10s内有第二个request进来就会被压进stack里面等待10s执行完成后再进一步处理下一个请求,后面的请求都会被挂起等待前面的同步执行完成后再执行,因此这也说明Node.js单线程的执行模型,由于这样的特性,咱们的页面不能有耗时很长的同步处理程序阻塞了程序的后续执行,而对于耗时过长的程序应该采用异步执行,这里也就是Node.js的第二个特性,异步。segmentfault


异步

咱们平时都会说Node.js是异步,可是所说的异步具体指的是什么异步?更进一步的说应该是主线程的异步处理函数队列+多线程异步I/O浏览器

主线程的异步处理函数队列

首先,所谓的主线程的异步处理函数队列指的是主线程的主要执行空间除了stack以及heap外,还有callback queue(回调函数队列),而callback queue是存放了异步处理的回调函数,在一个执行块里面,当里面的同步代码执行完成后,会从callback queue里面取出回调函数一个个执行,咱们最多见的异步处理函数就是setTimeout,一个简单的例子来说述:网络

function sleep(time) {
    var _exit = Date.now() + time * 1000;
    while( Date.now() < _exit ) {}
    return ;
}

function main(){
    setTimeout(function(){
        console.log('setTimeout run');
    },0);
    sleep(5);
    console.log('after sleep');
}

main();

/** 执行输出
    after sleep
    setTimeout run
**/

下面是代码块的主线程堆栈执行:多线程

主线程异步执行队列

看上图,主线程将main函数压进stack里面一行行解析执行,首先遇到setTimeout方法,由于setTimeout是一个异步处理函数,这里会把setTimeout(callback,timeout),里面的callback函数移进callback queue里面,同时会把本身从主线程的stack里面移除,继续压进后面的执行代码来解析执行,这里继续压进sleep沉睡5s,接下来执行console,等到这里的同步代码执行完成后这个时候就会从callback queue(FIFO顺序)里面取回调函数一个个执行。(题外话:就算setTimeout里面的timeout设置了是0,都是要等待执行块里面的同步代码执行完成后再去执行callback queue里面的代码)这就是异步里面的其一:主线程异步函数处理队列。框架

多线程异步I/O

一看标题多线程异步I/O可能会有疑问,不是说Node.js是单线程的吗?其实这里并无冲突,Node.js每一个进程里面只有一个主线程来处理程序,因此,主线程是单线程的。而主线程以外调用的I/O处理是经过一个叫作线程池来管理和分配线程来处理I/O,因此对I/O的处理是多线程。而主线程和I/O线程池则经过上面刚刚讲述的主线程的异步处理函数队列来协做。除了上文所说的timers模块里面的setTimeout函数外,Node.js还对文件系统、网络都实现了异步化调用(题外话:系统底层的I/O异步化都是基于c++的异步框架libuv来实现,而后往上层提供JavaScript调用接口)此处以前理解有误,应该是Node.js只对文件系统以及DNS实现了多线程I/O封装,网络I/O仍是采用单线程形式(可参考libuv设计概述原文:Important libuv uses a thread pool to make asynchronous file I/O operations possible, but network I/O is always performed in a single thread, each loop’s thread.感谢bd_bai指正)异步

异步I/O

而Node.js的高性能也是得益于其将阻塞的I/O异步化,使得不影响主逻辑的执行。


事件驱动

文章至此,先简单总结一下Node.js的两个简单特性,每一个Node.js进程只有一个主线程在执行程序代码,在执行的过程当中Node.js将阻塞的I/O异步化,并将其回调函数插入callback queue里面,等待同步逻辑执行完成后再经过callback queue里面取出回调函数压进stack里面执行。好了,而事件驱动的做用就是取出回调函数。事件驱动又叫事件循环,是指主线程从主线程的异步处理函数队列里面不停循环的读取事件,驱动了全部的异步回调函数的执行。

事件驱动

至此整个Node.js的异步化逻辑能够不断循环的跑起来了,以上则是咱们平常所言的Node.js的三大特性以及其原理。

相关文章
相关标签/搜索