Node 初探异步编程

Posted

    tags:

    篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Node 初探异步编程相关的知识,希望对你有一定的参考价值。

    从C/C++转过来最开始不适应的就是这个了吧。=-=

    Node是单线程,那么怎么提高效率?怎么解决一些阻塞问题?Node的基因里使用了异步IO,上次在http://www.cnblogs.com/zhangmingzhao/p/7564738.html 已经说到这个问题,Node的异步机制往往伴随着回调。

    先看一个关于CPU的例子来比较同步和异步:

       同步:CPU需要计算10个数据,每计算一个结果后,将其写入磁盘,等待写入成功后,再计算下一个数据,直到完成。

       异步:CPU需要计算10个数据,每计算一个结果后,将其写入磁盘,不等待写入成功与否的结果,立刻返回继续计算下一个数据,计算过程中可以收到之前写入是否成功的通知,直到完成。

    如过没有异步,CPU写入磁盘等待返回的结果的等待时间也被无情的消耗了。来看一段代码:

    我们期待的结果是1,但是结果确实0。为什么呢?

    这个就是node的异步机制。我们先执行了plus,没有等2秒,而是直接运行下一句打印函数,此时的c值还是0.

    那怎么解决这个问题呢?

    传入回调函数来解决。

    我们来给plus传入一个callback函数,如下:

    运行结果,打印出1。

    由于异步的高效性,node.js设计之初就考虑做为一个高效的web服务器,作者理所当然地使用了异步机制,并贯穿于整个node.js的编程模型中,新手在使用node.js编程时,往往会羁绊于由于其他编程语言的习惯,比如C/C++,觉得无所适从。我们可以从以下一段简单的睡眠程序代码窥视出他们的区别。

    我们来写一个Java的:

    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class Main {
        public static void main(String[] args) {
            int i;
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
            for(i = 0; i < 10; i++) {
                System.out.println(df.format(new Date()));
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    运用Java中线程的sleep来得到如下结果:

    2017-09-22 18:53:41
    2017-09-22 18:53:43
    2017-09-22 18:53:45
    2017-09-22 18:53:47
    2017-09-22 18:53:49
    2017-09-22 18:53:51
    2017-09-22 18:53:53
    2017-09-22 18:53:55
    2017-09-22 18:53:57
    2017-09-22 18:53:59

    我们把代码直译成Node版本:

    function test() {
        for (var i = 0; i < 10; i++) {
            console.log(new Date);
            setTimeout(function(){}, 2000);    //睡眠2秒,然后再进行一下次for循环打印
        }
    };
    test();

    我们发现这个结果几乎是同时打印出来,2秒并没有起到什么作用。

    setTimeout的第二个参数表示该时间之后在执行第一个参数表示的callback函数。

    因此我们可以分析, 由于Node.js的异步机制,setTimeout每个for循环到此之后,都注册了一个2秒后执行的回调函数然后立即返回马上执行console.log(new Date),导致了所有打印的时间都是同一个点。然而这个函数仅仅是注册,空函数什么事都没做。那么我这样呢:

    for (var i = 0; i < 10; i++) {
        setTimeout(function() {
            console.log(new Date);
        }, 2000);
    }

     

    结果是等了2秒后,一下子打印了10个

    还是异步特性,我们可以这样理解,for循环里每次setTimeout注册了2秒之后执行的一个打印时间的回调函数,没有等2秒,而是立即返回,再执行setTimeout,如此反复直到for循环结束,因为执行速度太快,导致同一个时间点注册了10个2秒后执行的回调函数,然后for循环结束,因此导致了2秒后所有回调函数的立即执行。 

     

     继续测试,我们加上 console.log("before FOR: " + new Date)和之后console.log("after FOR: " + new Date)

    console.log("before FOR: " + new Date)
    for (var i = 0; i < 10; i++) {
        setTimeout(function() {
            console.log(new Date);
        }, 2000);
    }
    console.log("after FOR: " + new Date)

    这个与一开始的例子差不多,中间的延迟不影响下面代码的运行。

    before和after几乎在同一时刻运行

    而定时器中的回调函数则按要求的2秒之后执行,也是同一秒内执行完毕。那么如何实现最初Java语言每隔2秒打印一个系统时间的需求函数呢,这样写,结果也可以达到要求:

    function test() {
        for (var i = 0; i < 10; i++) {
            console.log(new Date);
            wsleep(2000);    //睡眠2秒,然后再进行一下次for循环打印
        }
    };
    test();
    
    function wsleep(milliSecond) {
        var startTime = new Date().getTime();
        while(new Date().getTime() <= milliSecond + startTime) {
        }
    }

     

     

    但是在实际情况中是不会这么写的,这样阻塞了CPU20s的时间,而不能干其他事,异步的存在就是提高效率。

     

    还有这样写可能会更帮助理解:

    for (var i = 0; i < 10; i++) {
        setTimeout(function () {
            console.log(new Date());
        }, 2000*(i+1));
    }

    我们发现打印的都是结果i为0时注册的函数。

     

    以上是关于Node 初探异步编程的主要内容,如果未能解决你的问题,请参考以下文章

    初探asp.net异步编程

    第二部分:异步编程初探与reactor模式

    异步编程初探与reactor模式 | Twisted 教程

    七:初探异步编程

    异步编程初探async和await

    Node-异步编程