异步回调是如何实现的?

时间:2014-05-01 11:13:27

标签: asynchronous v8 language-implementation

所有语言如何实现异步回调?

例如在C ++中,需要有一个"监视线程"开始std::async。如果它在主线程中启动,则必须等待回调。

std::thread t{[]{std::async(callback_function).get();}}.detach();

v.s。

std::async(callback_function).get(); //Main thread will have to wait

JavaScript中的异步回调怎么样?在JS中大量使用回调... V8如何实现它们? V8是否会创建大量线程来监听它们并在收到消息时执行回调?或者它是否使用一个线程来监听所有回调并继续刷新?

例如,

setInterval(function(){},1000);
setInterval(function(){},2000);

V8是否创建了2个线程并监视每个回调状态,或者它有一个池来监视所有回调?

3 个答案:

答案 0 :(得分:6)

V8没有使用回调(包括setInterval)实现异步函数。 Engine简单地提供了一种执行JavaScript代码的方法。

作为V8嵌入器,您可以创建setInterval JavaScript函数,该函数链接到您的本机C ++函数,可以执行您想要的操作。例如,创建线程或安排一些工作。此时,您有责任在必要时调用提供的回调。一次只有一个线程可以使用V8引擎(V8隔离实例)来执行代码。这意味着如果需要从另一个线程调用回调,则需要同步。 V8提供锁定机制是你需要的。

解决此问题的另一种更常见的方法是为V8创建一个函数队列来执行并使用无限队列处理循环来在一个线程上执行代码。这基本上是一个事件循环。这样您就不需要使用执行锁,而是使用另一个线程将回调函数推送到队列。

所以它依赖于浏览器/ Node.js /其他嵌入器如何实现它。

答案 1 :(得分:0)

TL; DR:实现异步回调基本上是允许控制流继续进行而不会阻塞该回调。在最终调用回调函数之前,控制流可以自由执行不依赖于回调结果的任何操作,例如,调用者可以像回调函数已返回一样继续进行,或者调用者可以将其控制权交给其他函数。

由于问题是针对一般实现而不是特定语言,因此我的回答将尽量涵盖实现共性。

不同的语言对于异步回调具有不同的实现,但是原理是相同的。 关键是使控制流与执行的代码脱钩。它们对应于执行上下文(如带有运行时堆栈的控制线程)和执行的任务。传统上,执行上下文和执行的任务通常是1:1关联的。使用异步回调,它们可以解耦。

1。原则

要将控制流与代码分离,将每个异步回调视为条件任务会很有帮助。当代码注册异步回调时,它实际上在系统中安装了任务的条件。当满足条件时,将调用回调函数。为了支持这一点,需要一个条件监视机制和一个任务计划程序,以便

  1. 程序员不需要跟踪回调的条件;

  2. 在满足条件之前,程序可以继续执行不依赖于回调结果的其他代码,而不会阻塞条件;

  3. 一旦满足条件,就可以保证执行回调。程序员无需安排执行时间;

  4. 执行回调后,调用者可以访问其结果。

2。可移植性的实现

例如,如果您的代码需要处理来自网络连接的数据,则无需编写检查连接状态的代码。您仅注册一个回调,一旦数据可供处理,该回调将被调用。连接检查的繁琐工作留给了语言实现,众所周知,这特别棘手,特别是当我们谈论可伸缩性和可移植性时。

语言实现可以使用异步io,非阻塞io或线程池或任何技术来为您检查网络状态,一旦数据准备好,便计划执行回调函数。这里的代码控制流看起来像直接从回调注册转到回调执行,因为该语言隐藏了中间步骤。这是可移植的故事。

3。可扩展性的实现

隐藏肮脏的作品只是整个故事的一部分。另一部分是,您的代码本身不需要阻塞等待任务条件。当您同时有许多网络连接并且其中一些可能已经准备好数据时,等待一个连接的数据没有任何意义。您的代码的控制流可以简单地注册回调,然后继续执行其他任务(例如,满足条件的回调),只要知道已注册的回调在数据可用时仍会执行即可。

如果要满足回调条件不占用太多CPU(例如,等待计时器或等待来自网络的数据),并且回调函数本身是轻量级的,则使用单CPU(或单线程) )能够同时处理大量回调,例如传入的网络请求处理。这里的控制流看起来像是从一个回调跳到另一个回调。这是可伸缩性的故事。

4。并行实现

有时,对于非阻塞IO条件,回调不是挂起的,而是对于诸如页面错误之类的阻塞操作;或回调不依赖任何条件,而是纯计算逻辑。在这种情况下,异步回调不会节省您的CPU等待时间(因为没有空闲等待)。但是由于异步回调意味着回调函数可以与调用者或其他回调并行执行(受某些数据共享和同步约束),因此语言实现可以将回调任务分派给不同的线程,从而实现并行的好处。该平台具有多个硬件线程上下文。它仍然提高了可伸缩性。

5。实现生产力

当代码需要处理链接的回调时,即当回调以递归方式注册其他回调(称为回调地狱)时,异步回调的生产率可能不是很高。有救援方法。

可以探索异步回调的语义,以便将无望的嵌套回调替换为其他语言构造。基本上可以有两种不同的回调视图:

  1. 从数据流的角度来看:异步回调=事件+任务。 注册回调实际上会生成一个事件,该事件将发出 满足任务条件时。在这种情况下,链式 回调只是事件,其处理会触发其他事件 发射。它可以自然地在事件驱动中实施 编程,其中任务执行是由事件驱动的。诺言 可观察性和可观察性也可以视为事件驱动的概念。什么时候 同时准备好多个事件,它们的相关任务可以 也可以同时执行。

  2. 从控制流的角度来看:注册回调会产生 控制其他代码,并且回调执行仅恢复 一旦满足条件就控制流。在这种情况下,连锁 异步回调只是可恢复的功能。多 回调可以写成一个接一个的传统 “同步”方式,在两者之间(或等待)进行yield操作。它 实际上变成了协程

我还没有讨论异步回调与其调用者之间传递数据的实现,但是如果使用共享内存(调用者和回调可以共享数据)通常并不困难。实际上,Golang的渠道也可以考虑在yield / await中,但重点是数据传递。

答案 2 :(得分:0)

当API完成其工作时,传递给浏览器API的回调(如setTimeout)将被推入同一浏览器队列中。

当堆栈为空时,引擎可以检查此队列,然后将下一个回调推入JS堆栈以执行。

您不必监视API调用的进度,您可以要求它完成工作,并且完成后会将您的回调放入队列中。