同步和异步编程有啥区别(在node.js中)

Posted

技术标签:

【中文标题】同步和异步编程有啥区别(在node.js中)【英文标题】:What is the difference between synchronous and asynchronous programming (in node.js)同步和异步编程有什么区别(在node.js中) 【发布时间】:2013-04-26 12:38:00 【问题描述】:

我一直在阅读nodebeginner 我遇到了以下两段代码。

第一个:

    var result = database.query("SELECT * FROM hugetable");
    console.log("Hello World");

第二个:

    database.query("SELECT * FROM hugetable", function(rows) 
       var result = rows;
    );
    console.log("Hello World");

我明白他们应该做什么,他们查询数据库以检索查询的答案。然后console.log('Hello world')

第一个应该是同步代码。 第二个是异步代码。

这两部分的区别对我来说很模糊。输出会是什么?

谷歌搜索异步编程对我也没有帮助。

【问题讨论】:

Stage 你没有用谷歌找到任何东西,这是一个相当大的主题。在同步编程中,每一步都在前一步执行完毕后执行。在异步中,即使第 1 步未完成,也会执行第 2 步。您在第二个示例中定义的函数称为回调函数,一旦返回数据库的结果就会运行,这可能是在运行 console.log 之后。 @Bartdude 有很多关于异步编程的内容,但没有简单解释它是什么以及它在实践中的含义。 @GabrielLlamas 为什么要避免使用同步函数? @CharlieParker 因为它们阻塞了事件循环,您将失去异步事件 I/O 模型的所有好处。因为这是一种不好的做法。这样想:如果你不使用异步函数,为什么要使用 Node.js? @GabrielLlamas,如果我正在执行一个 INSERT 查询并且我想使用database.query() 之后最后插入的 ID,那么我应该将它称为同步,对吗?或者应该是什么方法? (这个问题我很久了) 【参考方案1】:

不同的是,在第一个例子中,程序会在第一行阻塞。下一行 (console.log) 将不得不等待。

第二个示例中,console.log 将在处理查询时执行。也就是说,查询将在后台处理,而您的程序正在做其他事情,一旦查询数据准备好,您将对其进行任何处理。

所以,简而言之:第一个示例会阻塞,而第二个不会。

以下两个例子的输出:

// Example 1 - Synchronous (blocks)
var result = database.query("SELECT * FROM hugetable");
console.log("Query finished");
console.log("Next line");


// Example 2 - Asynchronous (doesn't block) 
database.query("SELECT * FROM hugetable", function(result) 
    console.log("Query finished");
);
console.log("Next line");

应该是:

    Query finishedNext line Next lineQuery finished

注意 虽然 Node 本身是单线程,但也有一些任务可以并行运行。例如,文件系统操作发生在不同的进程中。

这就是 Node 可以执行异步操作的原因:一个线程执行文件系统操作,而 Node 主线程继续执行您的 javascript 代码。在像 Node 这样的事件驱动服务器中,文件系统线程通知主 Node 线程某些事件,例如完成、失败或进度,以及与该事件相关的任何数据(例如数据库查询的结果或错误消息)并且主节点线程决定如何处理该数据。

您可以在此处阅读更多信息:How the single threaded non blocking IO model works in Node.js

【讨论】:

所以基本上,当我执行第一段代码时,它会做这样的事情:request query.; 5 seconds later when the request is done; console.log;当第二个执行时:request query; console.log; work on the query; @JohnGalt sql 在不同的线程上运行。但这当然取决于您使用的 sql 驱动程序的实现。驱动程序应该产生一个新线程,连接到 mysql 并运行查询。完成后,将结果发布到event queue,Node 将调用回调。 异步示例不能输出与#1 相同的内容吗?例如,database.query 完成得如此之快,以至于当我们到达 console.log 时,任务已经完成。 @TheBronx 如果示例 2 中的 console.log("Next line"); 在匿名函数内部,那么就在 console.log("query finished"); 之后,这意味着在“查询完成”之后将打印“下一行”对吗?因此,如果我以嵌套方式拥有所有内容,那么所有内容都将以同步方式运行,因此我无需担心使用某些函数的同步版本。我的理解正确吗? 简答:是的@Abdul,你是对的。 长答案:嵌套函数(回调)是按顺序做事的方式,“一个接一个”。但这在技术上并不是“同步的”。匿名函数仍然在“阻塞操作完成时”执行,或者换句话说,“异步”执行。 Node.js 可以在阻塞操作发生时执行其他功能。函数保持异步,只是你正在链接它们。同步函数会阻塞执行,这是关键。【参考方案2】:

主要区别在于异步编程,否则您不会停止执行。您可以在发出“请求”时继续执行其他代码。

【讨论】:

【参考方案3】:

在同步的情况下,console.log 命令在 SQL 查询执行完成之前不会执行。

在异步情况下,console.log 命令将被直接执行。查询的结果将在之后的某个时间由“回调”函数存储。

【讨论】:

但是实际上是同时被调用的吗?让我困惑的是,在异步代码中,实际代码是同时并行运行的吗? 这取决于处理器(是多核吗?)和操作系统。见en.wikipedia.org/wiki/Multithreading_(software)#Multithreading【参考方案4】:

如果您在两个示例中添加一行,这将变得更加清晰:

var result = database.query("SELECT * FROM hugetable");
console.log(result.length);
console.log("Hello World");

第二个:

database.query("SELECT * FROM hugetable", function(rows) 
   var result = rows;
   console.log(result.length);
);
console.log("Hello World");

尝试运行这些,您会注意到第一个(同步)示例 result.length 将在“Hello World”行之前打印出来。 在第二个(异步)示例中,result.length 将(很可能)在“Hello World”行之后打印。

这是因为在第二个示例中,database.query 在后台异步运行,脚本直接以“Hello World”继续运行。 console.log(result.length) 仅在数据库查询完成时执行。

【讨论】:

您说:result.length 将(最有可能)在“Hello World”行之后打印。 ....为什么这只是“最有可能”?我认为它总是在 console.log 输出之后打印。感谢您的澄清:) @humanityANDpeace:这就是异步访问的全部意义所在:您不知道什么时候完成。也许它是一个快得离谱的数据库,并且数据库查询甚至在 Javascript 到达“Hello World”行之前就返回了......【参考方案5】:

这两种方式的区别如下:

同步方式: 它等待每个操作完成,然后才执行下一个操作。 对于您的查询: console.log() 命令在 & 之前不会执行,除非查询已完成执行以从数据库中获取所有结果。

异步方式: 它从不等待每个操作完成,而是仅在第一个 GO 中执行所有操作。一旦结果可用,将处理每个操作的结果。 对于您的查询: console.log() 命令将在 Database.Query() 方法之后不久执行。数据库查询在后台运行并在完成检索数据后加载结果。

用例

    如果您的操作没有像从数据库中查询大量数据那样做繁重的工作,那么请继续使用同步方式,否则使用异步方式。

    在异步方式中,您可以向用户显示一些进度指示器,而在后台您可以继续进行繁重的工作。这是 GUI 应用程序的理想方案。

【讨论】:

这是否意味着 db.query(cmd, callback) 正在同时运行(如在线程中)?它们是否同时运行? 在他的第二个示例中,查询是否有可能完成得如此之快以至于它在console.log 之前首先调用回调? @Fahmi 理论上是的,实际上几乎不可能【参考方案6】:

该函数使第二个异步。

第一个强制程序等待每一行完成它运行,然后下一个可以继续。第二个允许每条线一次一起(并且独立地)运行。

允许异步或并发的语言和框架(js、node.js)非常适合需要实时传输的事物(例如聊天、股票应用程序)。

【讨论】:

【参考方案7】:

首先,我意识到我回答这个问题迟了。

在讨论同步和异步之前,让我们先简单看看程序是如何运行的。

同步情况下,每个语句完成在下一个语句运行之前。在这种情况下,程序完全按照语句的顺序进行评估。

这就是 异步 在 JavaScript 中的工作方式。 JavaScript 引擎有两部分,一部分查看代码并将操作排入队列,另一部分处理队列。队列处理发生在一个线程中,这就是为什么一次只能发生一个操作。

当看到异步操作(如第二个数据库查询)时,会解析代码并将操作放入队列中,但在这种情况下,会注册一个回调以在此操作完成时运行。队列中可能已经有许多操作。队列前面的操作被处理并从队列中删除。一旦处理了数据库查询的操作,请求就会被发送到数据库,完成后回调将在完成时执行。此时,已“处理”该操作的队列处理器继续进行下一个操作 - 在这种情况下

    console.log("Hello World"); 

数据库查询仍在处理中,但 console.log 操作位于队列的最前面并得到处理。这是一个同步操作,立即执行,立即产生输出“Hello World”。一段时间后,数据库操作完成,只有在查询中注册的回调被调用和处理,将变量结果的值设置为行。

一个异步操作可能会导致另一个异步操作,第二个操作将被放入队列中,当它到达队列的前面时,它将被处理。调用使用异步操作注册的回调是 JavaScript 运行时在操作完成后返回操作结果的方式。

了解哪个 JavaScript 操作是异步的一个简单方法是注意它是否需要回调 - 回调是第一个操作完成时将执行的代码。在问题的两个例子中,我们可以看到只有第二种情况有回调,所以是两者的异步操作。情况并非总是如此,因为处理异步操作结果的方式不同。

要了解更多信息,请阅读承诺。 Promise 是处理异步操作结果的另一种方式。 Promise 的好处是编码风格更像是同步代码。

许多库,如 node 'fs',为某些操作提供同步和异步样式。在操作不需要很长时间并且不经常使用的情况下(例如读取配置文件的情况),同步样式操作将导致代码更易于阅读。

【讨论】:

【参考方案8】:

同步编程

C、C#、Java 等编程语言是同步编程,所以无论你写什么,都会按照你写的顺序执行。

-GET DATA FROM SQL.
//Suppose fetching data take 500 msec

-PERFORM SOME OTHER FUNCTION.
//Performing some function other will take 100 msec, but execution of other 
//task start only when fetching of sql data done (i.e some other function 
//can execute only after first in process job finishes).

-TOTAL TIME OF EXECUTION IS ALWAYS GREATER THAN (500 + 100 + processing time) 
msec

异步

NodeJs 提供了异步特性,它本质上是非阻塞的,假设在任何需要时间的 I/O 任务(获取、写入、读取)中,nodejs 不会保持空闲并等待任务完成,它将开始执行队列中的下一个任务,并且每当该任务完成时,它将使用回调通知。 以下示例将有所帮助:

//Nodejs uses callback pattern to describe functions.
//Please read callback pattern to understand this example

//Suppose following function (I/O involved) took 500 msec
function timeConsumingFunction(params, callback)
  //GET DATA FROM SQL
  getDataFromSql(params, function(error, results)
    if(error)
      callback(error);
    
    else
      callback(null, results);
    
  )


//Suppose following function is non-blocking and took 100 msec
function someOtherTask()
  //some other task
  console.log('Some Task 1');
  console.log('Some Task 2');


console.log('Execution Start');

//Start With this function
timeConsumingFunction(params, function(error, results)
    if(error)
      console.log('Error')
    
    else
      console.log('Successfull'); 
    
  )

//As (suppose) timeConsumingFunction took 500 msec, 
//As NodeJs is non-blocking, rather than remain idle for 500 msec, it will start 
//execute following function immediately
someOtherTask();

简而言之,输出如下:

Execution Start
//Roughly after 105 msec (5 msec it'll take in processing)
Some Task 1
Some Task 2
//Roughly After 510 msec
Error/Successful //depends on success and failure of DB function execution

区别很明显,同步肯定需要超过 600(500 + 100 + 处理时间)毫秒,异步可以节省时间。

【讨论】:

【参考方案9】:

同步函数是阻塞的,而异步函数不是。在同步函数中,语句在下一条语句运行之前完成。在这种情况下,程序会完全按照语句的顺序进行评估,如果其中一个语句花费了很长时间,程序的执行就会暂停。

异步函数通常接受回调作为参数,并在调用异步函数后立即在下一行继续执行。回调仅在异步操作完成且调用堆栈为空时调用。诸如从 Web 服务器加载数据或查询数据库之类的繁重操作应异步完成,以便主线程可以继续执行其他操作而不是阻塞,直到完成该长时间操作(在浏览器的情况下,UI 将冻结) .

Github 原贴:Link

【讨论】:

【参考方案10】:

JS中的异步编程:

同步

停止执行进一步的代码,直到完成为止。 因为它停止了进一步的执行,所以同步代码被称为“阻塞”。阻塞是指不会执行其他代码。

异步

this 的执行延迟到事件循环,这是 JS 虚拟机中的一个构造,它执行异步函数(在同步函数堆栈为空之后)。 异步代码称为非阻塞,因为它不会阻止进一步的代码运行。

例子:

// This function is synchronous
function log(arg) 
    console.log(arg)


log(1);

// This function is asynchronous
setTimeout(() => 
    console.log(2)
, 0);

log(3)
示例记录 1、3、2。 最后记录了 2,因为它在堆栈为空后执行的异步函数中。

【讨论】:

以上是关于同步和异步编程有啥区别(在node.js中)的主要内容,如果未能解决你的问题,请参考以下文章

阻塞与同步、非阻塞和异步有啥区别? [复制]

Node-异步编程

HTTP请求中同步与异步有啥不同

多线程的同步和互斥有啥区别

NSNotification是同步还是异步?和delegate相比有啥区别,效率呢?

Node.js 同步与异步编程