Nodejs的最主要的特点就是单线程、异步IO、事件驱动。
单线程:
单线程其实就是按从上到下顺序依次来执行,而且每次只执行一个任务,只有当前这个任务执行完了,才会执行下一个任务。在JS引擎中只有一个线程去解析和执行javascript代码,即主线程,这就是Nodejs为何是单线程的原因了。但实际上还有其他的线程:处理AJAX请求的线程、处理DOM事件的线程、定时器线程、读写文件的线程等。这些线程称为工作线程。
异步IO:
以下用一个图来描写异步IO
事件驱动:
线程驱动是当收到一个请求的时候,将会为该请求开一个新的线程来处理请求。而线程主要是由线程池来管理的。当线程池中有空闲的线程,会从线程池中拿取线程来处理,如果线程池中没有空闲的线程,新来的请求将会进入队列排队,直到线程池中空闲线程
事件驱动编程主要思想是通过事件或状态的变化来进行应用程序的流程控制,一般通过事件监听完成,一旦事件被检测到,则调用相应的回调函数。
事件驱动主要执行过程是当进来的一个新的请求的时候,请求将会被压入队列中,然后通过一个循环来检测队列中的事件状态变化,如果检测到有状态
变化的事件,那么就执行该事件对应的处理代码,一般都是回调函数。
下面先来看一个例子:
console.log("程序开始!");
setTimeout(function () {
console.log("执行第一个函数!")
},0);
setTimeout(function () {
console.log("执行第二个函数!")
},0);
console.log("程序结束!")
/*输出
程序开始!
程序结束!
执行第一个函数!
执行第二个函数!
*/
上面的例子说明了Nodejs是单线程运行、基于事件驱动的,先同步(即按从上到下的顺序)执行整个整个js文件中的代码(此时的事件循环是暂停的),当遇到异步函数(setTimeout计时函数)时,会从线程池中寻求有用的线程来执行该异步函数,当异步函数执行完,就将回调函数放入消息队列里面。当整个js文件执行完后,事件循环开始执行,从消息队列里面取消息,开始执行里面的回调函数。
下面再来看一个例子:
console.log("程序开始!");
/*模拟计算密集*/
for(var i = 0; i<1000000000; i++) {}
setTimeout(function () {
setTimeout(function(){console.log("执行第二个函数!")},2);
},0);
setTimeout(function () {
console.log("执行第一个函数!")
},0);
console.log("程序结束!")
/*输出
程序开始!
大概停顿了一会才输出以下的结果
程序结束!
执行第一个函数!
执行第二个函数!
*/
该例子只是在上一个例子的基础上加了一个for循环(模拟计算密集);明显发现程序在输出“程序开始!”后停顿了一会才输出下面的结果;这个例子充分说明了Nodejs是不合适用来开发有关计算密集的程序,因为整个javascript代码是运行在单线程中的,除了异步函数会从线程池中寻求可用的新线程浅执行,其他的代码则是在主线程去(同步)执行,当遇到计算密集的情况时,整个线程会被阻塞住,影响到整个程序运行的效率。