前言我们在学习Node.js的时候,估计看到最多的关于Node.js特性的词汇就是单线程、异步非阻塞、事件驱动。本文通过这些特征词汇深入探讨Node.js的特征。我们都知道Node.js的运行时是v8。在设计之初,v8被Chrome用来解析浏览器中的JavaScript语言。它最大的特点是单线程,在Node.js中使用v8也是为了这个非常重要的特性。什么是单线程?简单的说,一个进程只有一个线程,程序是顺序执行的,只有前一个程序执行完了,才会执行后面的程序。我们来看看Node.js对于http服务的模型:Node.js的单线程是指主线程是一个“单线程”,主线程按照编码顺序一步步执行程序代码。如果线程被占用,后续程序代码执行就会卡住。练习一段测试代码:varhttp=require('http');functionsleep(time){var_exit=Date.now()+time*1000;while(Date.now()<_exit){}return;}varserver=http.createServer(function(req,res){sleep(10);res.end('serversleep10s');});server.听(8080);下面是代码块的栈图:JavaScript是解析性语言,代码按照编码顺序逐行压入栈中执行。执行完成后移除,然后继续按下一行代码块执行。上面代码块的栈图,当主线程接受请求后,程序被推入睡眠执行块同步执行(我们假设这是程序的业务处理),如果有第二个请求进来10s内,会被压入栈中,等待10秒执行完毕,再进一步处理下一个请求。后面的请求会被挂起,等待前面的同步执行完成再执行。这也说明了Node.js的单线程执行模型。因为这个特性,我们的页面不能有耗时的同步处理程序阻塞后续程序的执行,耗时过长的程序应该异步执行,这就是Node.js的第二个特性,异步。异步我们通常说Node.js是异步的,那么异步到底是什么意思呢?再者,应该是主线程的异步处理函数队列+多线程异步I/O。主线程的异步处理函数队列首先,所谓主线程的异步处理函数队列是指主线程的主要执行空间,除了栈和堆之外,还有一个回调队列(回调函数队列),回调队列存放异步处理的回调函数在一个执行块中。当里面的同步代码执行时,会从回调队列中取出回调函数,一个一个执行。我们最常用的异步处理函数就是setTimeout。一个简单的例子来描述:functionsleep(time){var_exit=Date.now()+time*1000;while(Date.now()<_exit){}return;}functionmain(){setTimeout(function(){console.log('setTimeoutrun');},0);睡觉(5);console.log('aftersleep');}main();/**sleepsetTimeoutrun后的执行输出**/下面是代码块的主线程栈执行:看上图,主线程推送main函数入栈逐行分析执行。首先,它遇到了setTimeout方法,因为setTimeout是一个异步处理函数。这里setTimeout(callback,ti??meout),里面的回调函数会被移到callback队列中,同时会把自己从主线程的栈中移除,继续push到后面的执行代码中分析和执行。这里继续push进入sleep5秒,然后执行console。等到这里的同步代码执行完毕。回调函数将从回调队列(先进先出顺序)中取出并一个一个执行。(题外话:即使setTimeout中的timeout设置为0,也需要等待执行块中的同步代码执行完毕,才能执行回调队列中的代码)这是异步的其中之一:主线程异步函数处理队列。多线程异步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.设计概述原文:Importantlibuv使用线程池使异步文件I/O操作成为可能,但网络I/O始终在单个线程中执行,即每个循环的线程。感谢bd_bai指正)而且Node.js的高性能也是必须的,得益于阻塞I/O的异步化,不影响主逻辑的执行。事件驱动篇说到这里,我们简单总结一下Node.js的两个简单特性。每个Node.js进程只有一个主线程执行程序代码。在执行期间,Node.js异步化阻塞的I/O。并将其回调函数插入到回调队列中,等待同步逻辑执行完毕,再从回调队列中取出回调函数压入栈中执行。嗯,事件驱动的作用就是把回调函数拿出来。事件驱动,又称事件循环,是指主线程不断循环从主线程的异步处理函数队列中读取事件,驱动所有异步回调函数的执行。至此,整个Node.js的异步逻辑就可以连续循环运行了。以上就是我们天天挂在嘴边的Node.js的三大特性和原则。
