回调如何在非阻塞设计中工作?

How Do Callbacks work in Non-blocking Design?

查看了其他几个问题,但没有完全找到我要找的东西。我正在使用 Scala,但我的问题非常高级,因此希望对任何语言都一无所知。


常规场景:

  1. Thread A 运行 是一个函数,需要完成一些阻塞工作(比如数据库调用)。
  2. 该函数有一些非阻塞代码(例如 Scala 中的 Async 块)导致某种 'worker' Thread B(在不同的池中)获取I/O 任务。
  3. Thread A 中的方法完成返回一个 Future,该 Future 最终将包含结果,并且 Thread A 返回到它的池以快速获取另一个请求进行处理。

Q1。某处的某些线程通常必须等待?

我对非阻塞架构的理解是,常见的方法是在 I/O 上仍然有一些线程 waiting/blocking 在某处工作——这只是拥有不同池的情况不同的核心,以便少量请求处理线程可以管理大量并发请求,而无需等待 CPU 个核心。

这是一个正确的普遍理解吗?

Q2。回调如何工作?

在上述场景中 - Thread B 正在执行 I/O 工作将 运行 回调函数(由线程 A 提供)if/when I/O 工作已完成 - 完成 Future 并取得一些结果。

线程 A 现在停止做其他事情并且与原始请求不再有任何关联。未来的结果如何发送回客户端套接字?我知道不同的语言对这种机制有不同的实现,但在高层次上我目前的假设是(不管 language/framework)一些 framework/container 对象必须总是做某种编排,这样当Future 任务完成,结果被发送回处理请求的原始套接字。


我花了几个小时试图找到解释这一点的文章,但每篇文章似乎都只处理真正的底层细节。我知道我遗漏了一些细节,但我很难问出我的问题,因为我不太确定我遗漏了哪些部分:)

Q1:不会,至少在用户代码层面不会。希望您的异步 I/O 最终归结为异步内核 API(例如 select())。这反过来将使用 DMA 来执行 I/O 并在完成时触发中断。所以它至少在硬件层面是异步的。

Q2:线程B完成了Future。如果您使用 onComplete 之类的东西,那么线程 B 将在完成调用时触发它(可能通过创建一个新任务并将该任务交给线程池以供稍后接收)。如果另一个线程调用 Await 阻塞 Future,它将触发该线程恢复。如果还没有任何东西访问 Future,则不会发生任何特别的事情 - 该值位于 Future 中,直到有人使用它。 (有关详细信息,请参阅 PromiseCompletingRunnable - 它的可读性令人惊讶)。

My understanding of non-blocking architectures is that the common approach is to still have some Thread waiting/blocking on the I/O work somewhere

如果某个线程在某处被阻塞,则它不是真正的非阻塞架构。所以不,这并不是对它的正确理解。这并不意味着这一定是坏事。有时您只需要处理阻塞(例如使用 JDBC)。最好将其推入指定用于阻塞的固定线程池,而不是让整个应用程序遭受线程饥饿。

Thread A is now off doing something else and has no association any more with the original request. How does the Result in the Future get sent back to the client socket?

使用Futures,这真的取决于ExecutionContext。当您创建 Future 时,工作完成的位置取决于 ExecutionContext.

val f: Future[?] = ???
val g: Future[?] = ???
立即创建

fg,并将工作提交到ExecutionContext中的任务队列。在大多数情况下,我们无法保证哪个将首先实际执行或完成。您对这些价值观所做的事情很重要。显然,如果您使用 Await 来等待 Future 完成,那么我们将阻塞当前线程。如果我们 map 他们并对这些值做一些事情,那么我们又需要另一个 ExecutionContext 来提交任务。这为我们提供了一系列任务,每次我们操作 Future.

时,这些任务都会异步提交并重新提交给执行程序执行。

最终需要在该链的末尾有一些 onComplete 以 return 将该值传递给某些东西,无论它是写入流还是其他东西。即,它可能不在原始线程的手中。