Scala future 是否会长期阻塞?

Does Scala future blocks anyway for a long operation?

我们到处都可以读到,当执行长 运行 操作或阻塞操作时,最好使用特殊的执行上下文。阻塞操作,如访问数据库。我明白为什么。这是为了避免线程饥饿。我们不希望“8”个可用线程忙于某些最终可能 return 或继续阻塞的阻塞代码。它要么严重减慢应用程序速度,要么无限期地阻止它。

同时,我想知道 Spray 或 Play 之类的东西是如何实现的。事实上,让我们以客户端为例。发送请求后,我们会得到未来的响应。换句话说,请求是异步执行的。顺便说一句,这可能最终会成为一个很长的 运行 操作。但是,没有任何内容表明在这些情况下启动许多请求可能会导致线程饥饿。因此我想知道为什么在那种情况下这不是问题。他们有特殊的线程池吗?

我在书 "Learning concurrent programing in Scala" 中指出,在 Future 中使用 "Blocking {}" 语句块有助于其调度程序自动生成更多线程。难道是他们的处理方式?

接收请求也是如此,在游戏中我们要执行一个异步操作。如果想从此操作访问数据库,应使用 "Blocking {}" 语句块。如何执行那个动作是一个特殊的threadPool/ExecutionContext。

My assumption here is that they rely on the implicit.global ExecutionContext. Maybe i'm wrong. The Bottom line is. Making request is a long operation by default, how using spray for instance in your code, would handle it such that not to create a Thread Starvation in your code ?

我们使用不同的 ExecutionContext 吗?

编辑:刚刚发现这个简短的演示文稿 Don't Block - How to Mess Up Akka and Spray 恰好更好地说明了我在这里遇到的问题。

无论如何,如果有其他意见,我将不胜感激

编辑:这是我了解到在使用 future 时不知何故发生的事情:

def apply[T](body: =>T): Future[T] = impl.Future(body)  //here I have omitted the implicit ExecutorContext
impl.Future is an implementation of Future trait:

def apply[T](body: =>T)(implicit executor: ExecutionContext): scala.concurrent.Future[T] =
{
  val runnable = new PromiseCompletingRunnable(body)
  executor.prepare.execute(runnable)
  runnable.promise.future
}

PromiseCompletingRunnable 看起来像这样:

class PromiseCompletingRunnable[T](body: => T) extends Runnable {
val promise = new Promise.DefaultPromise[T]()

override def run() = {
  promise complete {
    try Success(body) catch { case NonFatal(e) => Failure(e) }
  }
} } 

取自:Clarification needed about futures and promises in Scala 我在书中红色了一些更简单和相似的东西 "Learning concurrent programing in Scala"

This to me means: there is a Thread in a ThreadPool, that dequeue that task and try to set a promise future value with the result of the execution of that task. If that is correct, i don't see how that task making an IO call does not block the run of that Thread.

我认为您不理解的是,当您使用阻塞 API 发出客户端请求时,线程阻塞,坐在那里什么也不做,直到响应返回。但是,如果您使用异步 API,在等待响应返回时,没有线程在等待。当它返回时,肯定会从执行上下文中拉出一个线程来完成工作,但这就是您希望线程做的事情 - 工作,而不是什么都不做。让数百个线程无所事事地等待 return 的客户端请求或数据库查询是一种资源浪费。因为它们不是免费的,所以您必须限制它们,这就是线程饥饿的来源。在异步框架中,只有在有工作要做时才使用线程。这意味着如果每个 CPU 核心有一个线程,如果您耗尽了线程池,则意味着您的 CPU 被 100% 使用,而在阻塞框架中,您可以仅用 10 个线程耗尽线程池% CPU 利用率。请记住,大多数普通 Web 应用程序将大部分时间花在执行 IO 上,即等待数据库调用或对 return 的 http 客户端调用。等待量与实际工作量的对比通常是一个数量级或更多。所以只在有工作要做时才使用线程是一个很大的优势。

以I/O操作为例,我认为唯一缺少link的是I/O多路复用,可以通过epoll或者kqueue来实现。

通过使用epoll/kqueue,一个线程可以同时等待多个I/O事件,如果[=24=,这个线程正在等待(饥饿) ] 的 I/O 个响应,但您看到只有这个线程在等待。

nginx和nodejs都在使用这种工作模式