问题 Node.js同步与异步


我正在学习node.js,我看到了2个同步和asycn程序的例子(同一个)。

我确实理解了回调的概念,但我试图理解第二个(异步)示例的好处,因为看起来他们两个做的完全相同,即使这个差异......

你能否详细说明为什么第二个例子会更好? 我很乐意得到更广泛的解释,这将有助于我理解这个概念。

谢谢!!

第一个例子:

var fs = require('fs');

function calculateByteSize() {
    var totalBytes = 0,
        i,
        filenames,
        stats;
    filenames = fs.readdirSync(".");
    for (i = 0; i < filenames.length; i ++) {
        stats = fs.statSync("./" + filenames[i]);
        totalBytes += stats.size;
    }
    console.log(totalBytes);
}

calculateByteSize();

第二个例子:

var fs = require('fs');

var count = 0,
    totalBytes = 0;

function calculateByteSize() {
    fs.readdir(".", function (err, filenames) {
        var i;
        count = filenames.length;

        for (i = 0; i < filenames.length; i++) {
            fs.stat("./" + filenames[i], function (err, stats) {
                totalBytes += stats.size;
                count--;
                if (count === 0) {
                    console.log(totalBytes);
                }
            });
        }
    });
}

calculateByteSize();

10140
2017-12-26 15:51


起源



答案:


您的第一个示例是阻止I / O.换句话说,在循环遍历每个文件之前,您需要等待readdir操作完成。然后,您需要阻止(等待)每个单独的sync stat操作才能运行,然后再转到下一个文件。之后没有代码可以运行 calculateByteSize() 打电话直到所有操作完成。

另一方面,异步(第二个)示例是使用回调模式的所有非阻塞。在这里,执行返回到之后 calculateByteSize() 调用fs.readdir后立即调用(但在回调运行之前)。 readdir任务完成后,它会对您的匿名函数执行回调。在这里,它循环遍历每个文件,并再次对fs.stat进行非阻塞调用。

第二个更有利。如果您可以假装对readdir或stat的调用可以在250ms到750ms之间完成(可能不是这种情况),那么您将等待对同步操作的串行调用。但是,异步操作不会导致您在每次调用之间等待。换句话说,循环读取readdir文件,如果同步执行,则需要等待每个stat操作完成。如果你是异步执行,则不必等待调用每个fs.stat调用。


8
2017-12-26 16:04



谢谢!很棒的答案。换句话说 - 在第二个例子中,我们可以在收到第一个文件的统计数据之前得到第三个文件的统计数据,对吧? - FED
哪里可以阅读更多关于幕后发生的事情?正如你的答案所解释的那样 - Karl Morrison
I / O绑定工作不需要CPU,因此阻塞等待它完成的线程是低效的。 Javascript在数据准备就绪时使用事件/回调,允许正在运行的线程利用CPU资源。 nodejs.org/en/docs/guides/event-loop-timers-and-nexttick - Patrick


答案:


您的第一个示例是阻止I / O.换句话说,在循环遍历每个文件之前,您需要等待readdir操作完成。然后,您需要阻止(等待)每个单独的sync stat操作才能运行,然后再转到下一个文件。之后没有代码可以运行 calculateByteSize() 打电话直到所有操作完成。

另一方面,异步(第二个)示例是使用回调模式的所有非阻塞。在这里,执行返回到之后 calculateByteSize() 调用fs.readdir后立即调用(但在回调运行之前)。 readdir任务完成后,它会对您的匿名函数执行回调。在这里,它循环遍历每个文件,并再次对fs.stat进行非阻塞调用。

第二个更有利。如果您可以假装对readdir或stat的调用可以在250ms到750ms之间完成(可能不是这种情况),那么您将等待对同步操作的串行调用。但是,异步操作不会导致您在每次调用之间等待。换句话说,循环读取readdir文件,如果同步执行,则需要等待每个stat操作完成。如果你是异步执行,则不必等待调用每个fs.stat调用。


8
2017-12-26 16:04



谢谢!很棒的答案。换句话说 - 在第二个例子中,我们可以在收到第一个文件的统计数据之前得到第三个文件的统计数据,对吧? - FED
哪里可以阅读更多关于幕后发生的事情?正如你的答案所解释的那样 - Karl Morrison
I / O绑定工作不需要CPU,因此阻塞等待它完成的线程是低效的。 Javascript在数据准备就绪时使用事件/回调,允许正在运行的线程利用CPU资源。 nodejs.org/en/docs/guides/event-loop-timers-and-nexttick - Patrick


在第一个示例中,node.js进程(单线程)在readdirSync的整个持续时间内阻塞,除了等待返回结果外无法执行任何其他操作。在第二个示例中,进程可以处理其他任务,并且事件循环将在结果可用时将其返回到回调的延续。因此,您可以通过使用异步代码来处理更高的总吞吐量 - 在第一个示例中等待readdir所花费的时间可能是执行代码实际花费的时间的数千倍,因此您浪费了99.9%或更多的CPU时间。


5
2017-12-26 16:02





在您的示例中,异步编程的好处确实不太明显。但是假设你的程序也需要做其他事情。请记住,您的JavaScript代码在单个线程中运行,因此当您选择同步实现时,程序除了等待IO操作完成外无法执行任何其他操作。当您使用异步编程时,您的程序可以执行其他重要任务,而IO操作在后台运行(在JavaScript线程之外)


2
2017-12-26 16:02





你能否详细说明为什么第二个例子会更好?我很乐意得到更广泛的解释,这将有助于我理解这个概念。

这都是关于网络服务器的并发性(因此名称为“节点”)。如果这是在构建脚本中,则第二个同步示例将“更好”,因为它更直接。并且给定一个磁盘,使其异步可能没有多少实际好处。

但是,在网络服务中,第一个同步版本将阻止整个过程并破坏节点的主要设计原则。随着并发客户端数量的增加,性能会变慢。然而,第二个异步示例的表现相对较好,因为在等待相对较慢的文件系统返回结果时,它可以同时处理所有相对较快的CPU操作。异步版本应该基本上能够使您的文件系统饱和,无论您的文件系统能够提供多少,节点都能够以该速率将其发送给客户端。


1
2017-12-26 16:05





这里有很多好的答案,但一定要阅读文档:

同步版本将阻止整个过程,直到它们完成 - 停止所有连接。

文档中有一个很好的同步与异步概述: http://nodejs.org/api/fs.html#fs_file_system


0
2017-12-26 16:03