关于Node.js你必须要知道的原理性知识

来源:互联网 发布:mac jmeter下载安装 编辑:程序博客网 时间:2024/05/01 08:32

前言

最近学习了Node.js,但是发现关于Node.js的原理性资料比较少。比如说Node.js的non-blocking I/O,event-driven,single-thread的具体机制是什么,single-threadnone-blocking是不是矛盾,event-loop具体是什么等等这些问题,网络上能讲到这些并给正确解释的很少。但如果认真点找,还是能够总结出来的,于是就有了这篇文章。

none-blockingevent-driven,single-thread释义

  • none-blocking即非阻塞,是指当Node.js处理I/O耗时操作时,是不会使后续的操作阻塞的。怎么理解呢?比如说下面的伪代码:

        doSomeOperations(); //做一些普通的非I/O操作。    doHttpRequest(url,callback);//网络请求,url是请求的网址,callback会在请求完在后调用。    doSomeOtherOperations();//做另一些普通的非I/O操作。

    其中的doHttpRequest(callback);在运行的时候,会去请求url,如果是阻塞式的话,doSomeOtherOperations();会在doHttpRequest(url,callback);请求到网页后才被运行,因为doHttpRequest(url,callback);阻塞了。而Node.js是none-block非阻塞式的,也即是说doSomeOtherOperations();不会等待doHttpRequest(url,callback);请求到网页后才被运行,而是直接就运行了。

  • event-driven即事件驱动,是指Node.js程序采用一种event事件加callback回调函数来监听事件的方式,程序通过写监听事件比如说myEventEmitter.on("event",callback),然后使用myEventEmitter.emit('event');来触发event,然后监听事件中的callback就会运行了。
  • single-thread即单线程,Node.js采用单线程架构,所以写程序也变得更简单。

none-blocking VS single-thread

我们知道单线程跟非阻塞是矛盾的,既然单线程了,又怎么会非阻塞呢?因为在计算机技术中,处理阻塞以更大地利用计算机资源所采取的方式就是多进程多线程,而现在Node.js这家伙既能做到单线程又能做到非阻塞,这难道不让人觉得奇怪吗?
在这里我们需要明确的是,我们在Node.js写的代码是运行在主线程上面的,而所谓的single-thread指的也是主线程的单线程。而实际上,Node.js底层是存在多线程的,Node.js底层采用了libuv库,libuv库具有线程池,Node.js的I/O操作会被交给libuv处理,libuv通过线程池给I/O操作开新线程。这也是Node.js可以做到none-blocking的机理。所以要记住的一点是,尽管Node.js是单线程的,但它底层还是大量地使用了多线程,多线程支撑了none-blocking的特性。

Event loop

我们知道Node.js的事件处理是在event loop里处理的,但event loop到底是什么,它的运行流程是怎么样的,相信如果要理解Node.js的运行过程,这些都是绕不开的问题,接下来我们来看一下什么是event loop

  • 首先我们来看一个图,还有一小段代码。
    Visual representation
while(queue.waitForMessage()){  queue.processNextMessage();}

图中Queue是消息队列(message queue),而Stack是函数调用的栈(call stack)。每一个消息(message)可以理解成一个event,event的产生会在message queue里入队一个message,每个message都有对应的callback(监听该event的函数)。而上面的代码段就代表了event loop

  • 所以结合上面的图片和代码,event loop还是比较好理解的。总的运行流程如下:
    Node.js在event loop中,

    while(queue.waitForMessage()){  queue.processNextMessage();}

    不断地检测message queue中是否有未被处理的message,若检测到,则把该message对应的callback压入Stack中,然后执行Stack中的callback,每个callback在执行完毕时会出栈,当Stack为空时,Node.js又再次检测是否有未处理的message,如此循环。

process.nextTick(callback),setImmediate(callback),setTimeout(callback,second)的区别。

在讲到这三个函数的区别之前,我们先来弄清楚几个概念:tick,timer

  • tick
    我们在写Node.js程序时,会遇到一个函数叫process.nextTick(callback), 那么,什么是tick呢?我们可以把event loop的每一次循环理解成一个tick,当调用process.nextTick(callback)时,会在当前处于处理状态的message后面新增一个message,并与函数里的callback相对应。当前的message处理完毕后,这个新增的message就会被处理,callback也就被调用了。

  • timer
    timer是操作系统的定时器,当使用setTimeout(callback,second)时,后面的参数second指定了多久后调用callback,时间由timer处理,当指定的时间到时,由操作系统通知Node.js,然后新增一个相应的message并入队到message queue。由于这个通知的过程,相应的任务可能不会马上执行,所以second的定时是不精准的,只是一个大概值,实际时间会稍大于这个值。

知道了这些概念之后,我们来比较这三个函数的区别:

  • 我们首先来看setImmediate(callback)的作用是什么。
    setImmediate(callback)并不需要用tick,timer来解释,它的行为就是在调用时,在message queue最末端入队一个新的messagecallback相对应,当处理完它之前的所有message后,这个新message就会被处理了。

  • 接下来我们来看setTimeout(callback,second)
    setTimeout(callback,second)setImmediate(callback)不同的地方是,setTimeout(callback,second)在定时时间到才新增一个messagemessage queue的最末端,而setImmediate(callback)在被调用时就已经新增这样的一个message了。于是同时调用setImmediate(callback)setTimeout(callback,0)时,前者的callback总要先运行后者的callback

  • 最后我们来看process.nextTick(callback),我们知道每个event loop的循环是一个tick,nextTick就是指下一个循环就执行,于是就是在当前message的后面新增一个message

因此,如果上面三个函数同时调用,执行先后顺序是process.nextTick(callback)setImmediate(callback)setTimeout(callback,second)

关于异步

我们知道Node.js是异步的,那么,异步指的是什么呢?Node.js中的异步并不仅仅指异步I/O(多线程),前面所说到的process.nextTick(callback),setImmediate(callback),setTimeout(callback,second)也是异步机制的一部分,它们把代码的执行延后了。

参考资料:

1.https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
2.http://blog.carbonfive.com/2013/10/27/the-javascript-event-loop-explained/
3.https://en.wikipedia.org/wiki/Node.js
4.http://mcgill-csus.github.io/student_projects/Submission2.pdf

0 0