我爱编程

Node.js中的异步

2017-10-29  本文已影响55人  靳晓阳s

title: Node.js中的异步
categories: tech
tags:


前言

写Node.js也有一段时间了,但一直没有理解Node.Js中的异步,同时我也有了更多的疑问异步回调和同步回调的区别,Node.js采用异步的优势是什么?如何才能优雅的写Node.js的代码?

Node.js的单线程

先来看下Node.js的结构,分为三层,如下:

struct.jpg

Node.js 虽然说是用的 Javascript,但只是在开发时使用 Javascript 的语法来编写程序。真正的执行过程还是由 V8 将 Javascript 解释,然后由 C/C++ 来执行真正的系统调用。

真正执行系统调用的其实是 Libuv。之前我们提到,Libuv 本身就是异步和事件驱动的,所以,当我们将 I/O 操作的请求传达给 Libuv 之后,Libuv 开启线程来执行这次 I/O 调用,并在执行完成后,传回给 Javascript 进行后续处理。

nodejs_thread.png

这里面的 I/O 包括文件 I/O 和 网络 I/O。两者的底层执行略有不同。从上面的 Libuv 官网的图中,我们可以看到,文件 I/O,DNS 等操作,都是依托线程池(Thread Pool)来实现的。而网络 I/O 这一大类,包括:TCP、UDP、TTY 等,是由 epoll、IOCP、kqueue 来具体实现的。

它的单线程指的是自身 Javascript 运行环境的单线程,Node.js 并没有给 Javascript 执行时创建新线程的能力,最终的实际操作,还是通过 Libuv 以及它的事件循环来执行的。

异步和同步

异步是对进程在和I/O(有文件I/O、网络I/O)交互而言的。在做网络请求时候,假设这个操作需要2s,同步会等待这个2s的请求完成再去做下一个动作;然后异步会直接进行下一个动作。

下面这段代码,输出的是null,因为网络I/O这个请求是异步的,执行到异步相关操作的时候会立即返回。

var statusCode = null;
request('https://www.baidu.com/', function(error, response, body) {
        var statusCode =  response.statusCode;
});
console.log(statusCode);

为什么Node.js要设计为异步

目前为止我接触的编程语言都是同步,刚开始写 Node.js 那是相当的不习惯,即使现在习惯了还是觉得这样编程很麻烦。既然如此那为何要把 Node.js 设计成异步的呢?

异步并不会少程序的运行时间,不会因为你用了异步,查询数据库的时间就会从10ms减少到5ms,无论是同步还是异步,执行的时间依然是10ms,异步的作用仅仅是提升了应用程序的吞吐量。进一步说,异步 IO 会减少单个请求的时间,去掉单个请求中那些无意义的等待时间(从用户角度来看)。所以单位时间内处理的请求没有变化(从服务器监督看),但每个请求的处理时间却减少了。从这个角度,服务器也节约了一些资源——即维持每个请求的连接消耗的内存。

异步这么麻烦应该怎么写

最简单的实现异步的方法就是回调,但是对于有时序性的,异步多了之后就成了回调陷阱,代码会像下面这个样子,瞬间感觉自己智商跟不上了要:

request('https://www.baidu.com/', function(error, response, body) {
    var statusCode =  response.statusCode;
    request('https://www.baidu.com/', function(error, response, body) {
        var statusCode =  response.statusCode;
        request('https://www.baidu.com/', function(error, response, body) {
            var statusCode =  response.statusCode;
        });
    });
});

当然现在有很多解决方案,如下图所示:

async.png

Node.js如何实现的异步

nodejs是单线程(single thread)运行的,通过一个事件循环(event-loop)来循环取出消息队列(event-queue)中的消息进行处理,处理过程基本上就是去调用该消息对应的回调函数。消息队列就是当一个事件状态发生变化时,就将一个消息压入队列中。

实际上,主线程只会做一件事情,就是从消息队列里面取消息、执行消息,再取消息、再执行。当消息队列为空时,就会等待直到消息队列变成非空。而且主线程只有在将当前的消息执行完成后,才会去取下一个消息。这种机制就叫做事件循环机制,取一个消息并执行的过程叫做一次循环。

用ajax请求做一个具体说明:

ajax.png

--EOF--

上一篇 下一篇

猜你喜欢

热点阅读