对不起,问一些听起来有点愚蠢的事情。我已经查了一堆文件,问了一些人,但我还是有点困惑。
所以我的问题是:
- 假设我当前有3个作业,其中两个(A、B)是同步作业,一个(C)是耗时的异步I/O绑定操作。
- 因此,首先,A和B在调用堆栈中,C在回调队列中。在A和B完成之后,C将被推送到调用堆栈中并开始执行。但是,当C运行时,进入新的同步作业D
- 现在,会发生什么?
我知道,如果C是CPU绑定的,那么执行将只是A-〉B-〉C-〉和D。我在网上或文档中发现,一旦一个作业开始执行,即使它是I/O绑定的,它仍然会首先完成它。但如果是这样,那么为什么javascript仍然是一种非阻塞语言?
另外,我在我的经验中发现,在我的express服务器中,如果我有一个函数在数据库中花费很长时间进行查询,并且刚刚输入了另一个请求,它不会被查询阻塞。
2条答案
按热度按时间agxfikkp1#
但在C运行时,会输入一个新的同步作业D
如果D是同步的,则在调用D的时刻,任何其他依赖于某个异步对象的任务(例如等待网络请求返回)将无法处理其回调的可能完成,直到D运行到最后并完成。因此,您可以将D视为一个 * 阻塞 * 函数-就像所有同步函数一样,一旦它被调用,它将阻止任何其他东西运行,直到它完成并且控制流被退回给调用者。
所以
为什么javascript仍然是一种非阻塞语言?
这不是一种非常准确的表达方式--很可能会编写代价高昂的阻塞同步代码,从而导致UI问题。
举个更具体的例子:
在上面,当
c
被调用时,fetch
发起了一个网络请求。但是随后doSomethingExpensive
被调用,这是一个代价高昂的同步函数。由于doSomethingExpensive
是同步的,所以行// do something with result
将不能运行 * 即使网络请求在此期间实际上已经完成 *,直到doSomethingExpensive
完全完成。也就是说,像上面的
doSomethingExpensive
这样的同步函数在完成之前实际上是“昂贵的”,消耗了大量的CPU,在大多数情况下是很少见的,相反,廉价的异步任务更常见。另外,我在我的经验中发现,在我的express服务器中,如果我有一个函数在数据库中花费很长时间进行查询,并且刚刚输入了另一个请求,它不会被查询阻塞。
这个查询是一个例子,它要么是廉价的(只需等待外部数据库产生响应),要么是在JavaScript之外实现的,其操作不会由于计算环境是多线程的而阻止其他JavaScript的执行。
wydwbb8l2#
一旦作业开始执行,即使它是I/O绑定的,它仍将首先完成它
你对非阻塞有一点误解。不管体系结构是阻塞的还是非阻塞的,这种描述都是正确的。非阻塞实际上意味着等待事件使用中断而不是轮询循环。
这是你误解的一个明显迹象:
在A和B完成之后,C将被压入调用堆栈并开始执行
错误率为99.99%。在A和B完成之后,C不会被推入调用堆栈。这强烈暗示您误解了等待I/O的实际作用。
现在,运行库实际处理A、B和C的方式取决于您最初调用它们的方式。
例如,假设有一个异步函数
Z
,它被配置为执行C
。然后A会先被执行,然后B会被执行,再然后Z会把C放到事件队列中(它被称为队列,但实际上它只是一个事件处理程序列表)。
当Z等待的事件发生时,运行时将调用C。这与常规函数调用完全相同(就像您调用
C()
一样),但它不是由您的代码调用,而是由处理事件的代码调用。另一方面,如果你这样做:
首先执行Z,然后将C添加到事件队列中。在将C添加到事件队列中之后,执行A,然后执行B。
当Z等待的事件发生时,运行时将调用C。如果碰巧Z等待的事件在A的执行期间发生,则运行时甚至不会注意到,并且将完成A和B的执行。在A和B的执行结束时,运行时将检查事件循环,并且将立即发现它需要调用C。
所以在这种不太可能的情况下,yes- C会在A和B完成后执行,但这只是一个巧合,C被执行的原因不是因为A和B完成,而是因为它等待的事件发生了。
要了解更多关于这一切如何运作的细节,你可能想看看我以前对其他问题的一些回答:
我知道回调函数异步运行,但为什么呢?
node.js如何调度异步和同步任务?