javascript 的并发模型的效率

efficiency of javascript's concurrency model

众所周知,JS在单线程上使用事件循环来模拟多线程。我对它的效率感到困惑。

假设我们想要读取一个文件,并且有一种方法可以使用 linux read 系统函数。据我所知,虽然 read 函数是 运行,但 CPU 会将作业发送到 DMA,以便 CPU 可以做其他事情。

在其他真正的多线程语言如C++中,我们创建一个新的线程来读取文件。当 DMA 处理文件时,CPU 确实可以从该文件读取线程切换到其他工作。

但在 JS 中,只有一个线程真正起作用。即使现在你把这个阅读文件的工作搁置了,但将来你必须在这个线程上做那个工作。每当你想调用 read 函数时,你必须已经在线程的堆栈中,即使 CPU 可以将工作留给 DMA,线程仍然阻塞在那里并且不能跳转到其他线程(因为只有一个线程,对吧?)

所以我觉得JS的事件循环模型并不是真正的并发,对吧?即使它可以暂时搁置工作,它仍然需要同样多的时间来处理工作。总执行时间不会减少。我很困惑,请帮助我。

您可以使用 web-workers 在 javascript 中 运行 真正的 multi-threading。您无法在 single-threaded 环境中模拟 multi-threading。异步和多线程不是一回事。您可以 运行 在 javascript 中异步,但不能 multi-threading(没有 WebWorkers)。

即使 JavaScript 是 single-threaded(由于 Web Workers 而不再是),您也可以要求操作系统同时执行许多 I/O 操作:

我们可以使用 non-blocking I/O,而不是使用阻塞 I/O,我们要求 OS 让线程休眠直到数据到达这里我们要求 OS 获取数据,并在数据到达时通知我们,同时允许我们做其他事情。

例如,在 node.js 文件系统 API 中,每个函数都存在多种形式:

  • 同步 APIs 同步执行所有操作,阻塞事件循环,直到操作完成或失败。
  • 回调 APIs 异步执行所有操作,不会阻塞事件循环,然后在完成或错误时调用回调函数,(回调 APIs 使用底层 Node.js 线程池在事件循环线程之外执行文件系统操作。
  • Promises APIs,其工作方式类似于回调 APIs,但更易于使用。

所以我们可以选择是要休眠直到数据到达这里,还是在那段时间做其他事情。

值得注意的是,实际硬件是 non-blocking。例如,Direct Memory Access 允许磁盘将数据放入内存而不涉及 CPU,并在传输完成后中断 CPU。