具有I/O绑定操作的javascript事件循环

jjjwad0x  于 2023-03-11  发布在  Java
关注(0)|答案(2)|浏览(95)

对不起,问一些听起来有点愚蠢的事情。我已经查了一堆文件,问了一些人,但我还是有点困惑。
所以我的问题是:

  • 假设我当前有3个作业,其中两个(A、B)是同步作业,一个(C)是耗时的异步I/O绑定操作。
  • 因此,首先,A和B在调用堆栈中,C在回调队列中。在A和B完成之后,C将被推送到调用堆栈中并开始执行。但是,当C运行时,进入新的同步作业D
  • 现在,会发生什么?

我知道,如果C是CPU绑定的,那么执行将只是A-〉B-〉C-〉和D。我在网上或文档中发现,一旦一个作业开始执行,即使它是I/O绑定的,它仍然会首先完成它。但如果是这样,那么为什么javascript仍然是一种非阻塞语言?
另外,我在我的经验中发现,在我的express服务器中,如果我有一个函数在数据库中花费很长时间进行查询,并且刚刚输入了另一个请求,它不会被查询阻塞。

agxfikkp

agxfikkp1#

但在C运行时,会输入一个新的同步作业D
如果D是同步的,则在调用D的时刻,任何其他依赖于某个异步对象的任务(例如等待网络请求返回)将无法处理其回调的可能完成,直到D运行到最后并完成。因此,您可以将D视为一个 * 阻塞 * 函数-就像所有同步函数一样,一旦它被调用,它将阻止任何其他东西运行,直到它完成并且控制流被退回给调用者。
所以
为什么javascript仍然是一种非阻塞语言?
这不是一种非常准确的表达方式--很可能会编写代价高昂的阻塞同步代码,从而导致UI问题。
举个更具体的例子:

const doSomethingExpensive = () => {
  for (let i = 0; i < 1e12; i++) {
    // something here
  }
};
const c = () => {
  fetch('/endpoint')
    .then(res => res.text())
    .then((result) => {
      // do something with result
    });
  doSomethingExpensive();
};

在上面,当c被调用时,fetch发起了一个网络请求。但是随后doSomethingExpensive被调用,这是一个代价高昂的同步函数。由于doSomethingExpensive是同步的,所以行// do something with result将不能运行 * 即使网络请求在此期间实际上已经完成 *,直到doSomethingExpensive完全完成。
也就是说,像上面的doSomethingExpensive这样的同步函数在完成之前实际上是“昂贵的”,消耗了大量的CPU,在大多数情况下是很少见的,相反,廉价的异步任务更常见。
另外,我在我的经验中发现,在我的express服务器中,如果我有一个函数在数据库中花费很长时间进行查询,并且刚刚输入了另一个请求,它不会被查询阻塞。
这个查询是一个例子,它要么是廉价的(只需等待外部数据库产生响应),要么是在JavaScript之外实现的,其操作不会由于计算环境是多线程的而阻止其他JavaScript的执行。

wydwbb8l

wydwbb8l2#

一旦作业开始执行,即使它是I/O绑定的,它仍将首先完成它
你对非阻塞有一点误解。不管体系结构是阻塞的还是非阻塞的,这种描述都是正确的。非阻塞实际上意味着等待事件使用中断而不是轮询循环。
这是你误解的一个明显迹象:
在A和B完成之后,C将被压入调用堆栈并开始执行
错误率为99.99%。在A和B完成之后,C不会被推入调用堆栈。这强烈暗示您误解了等待I/O的实际作用。
现在,运行库实际处理A、B和C的方式取决于您最初调用它们的方式。
例如,假设有一个异步函数Z,它被配置为执行C

A();
B();
Z().then(C);

然后A会先被执行,然后B会被执行,再然后Z会把C放到事件队列中(它被称为队列,但实际上它只是一个事件处理程序列表)。
当Z等待的事件发生时,运行时将调用C。这与常规函数调用完全相同(就像您调用C()一样),但它不是由您的代码调用,而是由处理事件的代码调用。
另一方面,如果你这样做:

Z().then(C);
A();
B();

首先执行Z,然后将C添加到事件队列中。在将C添加到事件队列中之后,执行A,然后执行B。
当Z等待的事件发生时,运行时将调用C。如果碰巧Z等待的事件在A的执行期间发生,则运行时甚至不会注意到,并且将完成A和B的执行。在A和B的执行结束时,运行时将检查事件循环,并且将立即发现它需要调用C。
所以在这种不太可能的情况下,yes- C会在A和B完成后执行,但这只是一个巧合,C被执行的原因不是因为A和B完成,而是因为它等待的事件发生了。
要了解更多关于这一切如何运作的细节,你可能想看看我以前对其他问题的一些回答:
我知道回调函数异步运行,但为什么呢?
node.js如何调度异步和同步任务?

相关问题