我知道回调函数异步运行,但为什么呢?

I know that callback function runs asynchronously, but why?

语法的哪一部分提供了该函数应该运行在其他线程中并且是非阻塞的信息?

让我们考虑 node.js

中的简单异步 I/O
 var fs = require('fs');
 var path = process.argv[2];

  fs.readFile(path, 'utf8', function(err,data) {
   var lines = data.split('\n');
   console.log(lines.length-1);
  });

究竟是什么使它在后台发生的把戏?任何人都可以准确地解释它或将 link 粘贴到一些好的资源吗?我到处都看到了很多关于回调是什么的信息,但没有人解释为什么它实际上是这样工作的。

这不是关于node.js的具体问题,而是关于每种编程语言中回调的一般概念。

编辑:

可能我提供的例子在这里不是最好的。所以我们不要考虑这个 node.js 代码片段。我一般在问 - 是什么让程序在遇到回调函数时继续执行的技巧。什么是语法 这使得回调概念成为非阻塞概念?

提前致谢!

回调不一定是异步的。执行完全取决于 fs.readFile 决定如何处理函数参数。

在 JavaScript 中,您可以使用例如 setTimeout.

异步执行函数

讨论和资源:

How does node.js implement non-blocking I/O?

Concurrency model and Event Loop

Wikipedia:

There are two types of callbacks, differing in how they control data flow at runtime: blocking callbacks (also known as synchronous callbacks or just callbacks) and deferred callbacks (also known as asynchronous callbacks).

首先,如果某事不是异步的,则意味着它正在阻塞。所以 javascript runner 在该行停止,直到该函数结束(这就是 readFileSync 会做的)。

众所周知,fs 是一个 IO 库,所以这种事情需要时间(告诉硬件读取一些文件不是马上完成的事情),所以做任何事情都很有意义不只需要 CPU,它是异步的,因为它需要时间,并且不需要冻结其余代码来等待另一个硬件(而 CPU 空闲)。

希望能解决您的疑惑

语法中 nothing 告诉您回调是异步执行的。回调可以是异步的,例如:

setTimeout(function(){
    console.log("this is async");
}, 100);

也可以是同步的,如:

an_array.forEach(function(x){
    console.log("this is sync");
});

那么,你怎么知道一个函数是同步调用还是异步调用回调呢?唯一可靠的方法是阅读文档。

您还可以编写一个测试来确定文档是否可用:

var t = "this is async";
some_function(function(){
    t = "this is sync";
});

console.log(t);

异步代码的工作原理

Javascript 本身没有任何使函数异步的功能。如果你想写一个异步函数,你有两个选择:

  1. 使用另一个异步函数,例如 setTimeout 或网络工作者来执行您的逻辑。

  2. 用C写的

至于C代码函数(如setTimeout)是如何实现异步执行的?这一切都与事件循环有关(或大部分)。

事件循环

在网络浏览器中有一段用于联网的代码。最初,网络代码只能下载一个东西:HTML 页面本身。当 Mosaic 发明 <img> 标签时,网络代码演变为下载多个资源。然后 Netscape 实现了 progressive rendering 图像,他们必须使网络代码异步,以便他们可以在加载所有图像之前绘制页面,并逐步和单独地更新每个图像。这就是事件循环的由来。

在浏览器的核心有一个从异步网络代码演变而来的事件循环。因此,它使用 I/O 原语作为其核心也就不足为奇了:select()(或类似的东西,如 poll、epoll 等,取决于 OS)。

C 中的 select() 函数允许您在单个线程中等待多个 I/O 操作,而无需生成额外的线程。 select() 看起来像:

select (max, readlist, writelist, errlist, timeout)

要让它等待 I/O(来自套接字或磁盘),您需要将文件描述符添加到 readlist,当有可用数据时它将 return在您的任何 I/O 频道上。一旦它 returns 您可以继续处理数据。

javascript 解释器保存您的回调,然后调用 select() 函数。当 select() return 时,解释器找出哪个回调与哪个 I/O 通道关联,然后调用它。

方便的是,select() 还允许您指定一个 timeout 值。通过仔细管理传递给 select()timeout,您可以在将来的某个时间调用回调。这就是 setTimeoutsetInterval 的实现方式。解释器保留所有超时的列表,并计算它需要传递的内容作为 timeoutselect()。然后当 select() returns 除了找出是否有任何由于 I/O 操作而需要调用的回调之外,解释器还会检查是否有任何需要调用的过期超时.

所以仅select()就涵盖了实现异步函数所必需的几乎所有功能。但是现代浏览器也有网络工作者。对于网络工作者,浏览器生成线程以异步执行 javascript 代码。要与主线程通信,worker 仍必须与事件循环(select() 函数)进行交互。

Node.js 在处理 file/disk I/O 时也会生成线程。 I/O 操作完成后,它会与主事件循环进行通信,以执行适当的回调。


希望这能回答您的问题。我一直想写这个答案,但之前太忙了。如果您想了解更多关于 C 中的非阻塞 I/O 编程的信息,我建议您阅读以下内容:http://www.gnu.org/software/libc/manual/html_node/Waiting-for-I_002fO.html

有关详细信息,另请参阅:

  • Is nodejs representing Reactor or Proactor design pattern?
  • Performance of NodeJS with large amount of callbacks