【问题标题】:Is synchronous HTTP request wrapped in a Future considered CPU or IO bound?是否将同步 HTTP 请求包装在 Future 考虑 CPU 或 IO 绑定?
【发布时间】:2020-11-15 22:46:56
【问题描述】:

考虑以下两个 sn-ps,其中第一个使用 Future 包装 scalaj-http 请求,而第二个使用 async-http-client

Sync 客户端使用全局 EC 封装在 Future 中

object SyncClientWithFuture {
  def main(args: Array[String]): Unit = {
    import scala.concurrent.ExecutionContext.Implicits.global
    import scalaj.http.Http
    val delay = "3000"
    val slowApi = s"http://slowwly.robertomurray.co.uk/delay/${delay}/url/https://www.google.co.uk"
    val nestedF = Future(Http(slowApi).asString).flatMap { _ =>
      Future.sequence(List(
        Future(Http(slowApi).asString),
        Future(Http(slowApi).asString),
        Future(Http(slowApi).asString)
      ))
    }
    time { Await.result(nestedF, Inf) }
  }
}

Async 客户端使用全局 EC

object AsyncClient {
  def main(args: Array[String]): Unit = {
    import scala.concurrent.ExecutionContext.Implicits.global
    import sttp.client._
    import sttp.client.asynchttpclient.future.AsyncHttpClientFutureBackend
    implicit val sttpBackend = AsyncHttpClientFutureBackend()
    val delay = "3000"
    val slowApi = uri"http://slowwly.robertomurray.co.uk/delay/${delay}/url/https://www.google.co.uk"
    val nestedF = basicRequest.get(slowApi).send().flatMap { _ =>
      Future.sequence(List(
        basicRequest.get(slowApi).send(),
        basicRequest.get(slowApi).send(),
        basicRequest.get(slowApi).send()
      ))
    }
    time { Await.result(nestedF, Inf) }
  }
}

sn-ps 正在使用

前者需要 12 秒,而后者需要 6 秒。似乎前者的行为好像受 CPU 限制,但我不明白这是怎么回事,因为Future#sequence 应该并行执行 HTTP 请求?为什么包装在 Future 中的同步客户端的行为与正确的异步客户端不同?异步客户端在幕后将调用包装在 Futures 中,难道不是这种情况吗?

【问题讨论】:

  • AFAIK,它应该被认为是 IO 有界的,因为线程除了等待不会做任何事情。适当的异步客户端不会阻塞真正的计算线程,因此 ec 应该不会进行更多调用,而 sycn 客户端会阻塞线程。 - 顺便说一句,您可能希望使用traverse,因此在定义列表时不会启动请愿,您可能希望使用同步客户端添加三分之一的sn-p,但包装在scala concurrent.blocking 块上。
  • @LuisMiguelMejíaSuárez 行为不是由所使用的执行上下文类型决定的吗,既然我们对两个客户端使用相同的global EC,那么行为是否应该不同?
  • 为什么?就像说如果我们使用 JVM 运行两个相似但具有不同复杂性的函数应该执行相同的函数。 - 顺便说一句,global 在您的环境中的性质是什么?您是否在只有一个线程的环境中运行?
  • 嗯,是的,我相信 Scastie 使用单个线程作为其global。 - 真正的异步客户端应该比blocking 做得更好,因为blocking (当工作时,例如对于单线程 ec 它什么都不做) 只会创建一个新线程来阻塞.而真正的异步客户端不应该阻塞任何东西,而是使用回调来工作。看看 JS 中的事件循环,你不能阻塞它,你也不能创建一个新的来阻塞,一切都必须是真正的异步。
  • @LuisMiguelMejíaSuárez println(scala.concurrent.ExecutionContext.Implicits.global) 在 scastie 中提供 parallelism = 6

标签: scala concurrency future scalaj-http sttp


【解决方案1】:

Future#sequence 应该并行执行 HTTP 请求吗?

首先,Future#sequence 不执行任何操作。它只是产生一个在所有参数完成时完成的未来。 如果 EC 中有空闲线程,则立即开始构建期货的评估(执行)。否则,它只是将其提交给某种队列。 我敢肯定,在第一种情况下,您可以单线程执行期货。

println(scala.concurrent.ExecutionContext.Implicits.global) -> 并行度 = 6

不知道为什么会这样,可能其他5个线程由于某种原因总是很忙。您可以使用 5-10 个线程来试验显式创建的新 EC。

与异步情况的不同之处在于您不自己创建未来,它由库提供,内部不会阻塞线程。它启动异步过程,“订阅”结果,并返回未来,当结果到来时完成。

实际上,异步库内部可能有另一个 EC,但我怀疑。

顺便说一句,如果没有blocking,Futures 不应该包含慢速/io/阻塞评估。否则,您可能会阻塞主线程池 (EC),您的应用将完全冻结。

【讨论】:

    猜你喜欢
    • 2015-06-08
    • 2014-07-02
    • 1970-01-01
    • 1970-01-01
    • 2015-11-21
    • 2018-12-31
    • 2017-10-24
    • 2019-07-05
    • 2016-10-17
    相关资源
    最近更新 更多