Считается ли синхронный HTTP-запрос, заключенный в Future, привязкой к процессору или вводу-выводу?
Рассмотрим следующие два фрагмента, в которых сначала запросы scalaj-http заключаются в Future
, а второй использует async-http-client
Клиент синхронизации в оболочке Future с использованием глобального EC
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) }
}
}
Асинхронный клиент с использованием глобального 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) }
}
}
Фрагменты используют
- Медленно для имитации медленного API
- scalaj-http
- бэкэнд sttp async-http-client
- время
Первое занимает 12 секунд, второе - 6 секунд. Кажется, что первый ведет себя так, как будто он связан с процессором, но я не понимаю, как это обстоит так, посколькуFuture#sequence
должен выполнять HTTP-запросы параллельно? Почему синхронный клиент завернут вFuture
вести себя иначе, чем обычный асинхронный клиент? Разве не тот случай, когда асинхронный клиент делает то же самое, когда он скрывает вызовы в Futures?
1 ответ
Будущая последовательность # должна выполнять HTTP-запросы параллельно?
Прежде всего, Future#sequence
ничего не выполняет. Он просто создает будущее, которое завершается, когда завершаются все параметры. Оценка (исполнение) построенных фьючерсов начинается немедленно, если в EC есть свободный поток. В противном случае он просто отправляет его в своего рода очередь. Я уверен, что в первом случае у вас будет однопоточное исполнение фьючерсов.
println (scala.concurrent.ExecutionContext.Implicits.global) -> parallelism = 6
Не знаю, почему это так, возможно, другие 5 потоков по какой-то причине всегда заняты. Вы можете поэкспериментировать с явно созданным новым EC с 5-10 потоками.
Разница со случаем Async в том, что вы не создаете будущее самостоятельно, оно предоставляется библиотекой, которая внутренне не блокирует поток. Он запускает асинхронный процесс, "подписывается" на результат и возвращает будущее, которое завершается, когда приходит результат.
На самом деле у async lib внутри может быть другой EC, но я сомневаюсь.
Кстати, фьючерсы не должны содержать медленных /io/ блокирующих оценок без blocking
. В противном случае вы потенциально заблокируете пул основных потоков (EC), и ваше приложение будет полностью заморожено.