Почему Scala Future работает последовательно при отображении через итератор - PullRequest
2 голосов
/ 12 января 2020

Так что я озадачен, почему сопоставление итератора с Futures заставит его работать последовательно. Рассмотрим следующий код -

import org.scalameter._

object IteratorVsListInFutures extends App {
  def slowFunction = Thread.sleep(1000)

  import scala.concurrent.ExecutionContext.Implicits.global
  import scala.concurrent._
  import duration._

  println("Should take approximately 4000 ms or 4 sec")
  println{
    withWarmer(new Warmer.Default) measure {
      List(1,2,3,4).foreach(_ => slowFunction)
    }
  }

  println("Should take approximately 1 second")
  println {
    withWarmer(new Warmer.Default) measure {
      val futures: Seq[Future[Unit]] = List(1,2,3,4).map(_ => Future { slowFunction})
      futures.foreach(x => Await.result(x, 10.seconds))
    }
  }

  println("And how long does this take")
  println {
    withWarmer(new Warmer.Default) measure {
      val futures = List(1,2,3,4).iterator.map(_ => Future { slowFunction})
      futures.foreach(x => Await.result(x, 10.seconds))
    }
  }

}

Я получаю следующие результаты -

Should take approximately 4000 ms or 4 sec
4012.132085 ms
Should take approximately 1 second
1004.997573 ms
And how long does this take
4016.533206 ms

Process finished with exit code 0

Первый тест соответствует примерно 4 секундам, второй тест - примерно 1 секунде ( потому что фьючерсы выполняются параллельно) Что меня смущает, так это то, что 3-й бенчмарк тоже около 4 секунд?

1 Ответ

5 голосов
/ 12 января 2020

Из-за нестрогого характера итераторов следующее

List(1,2,3,4).iterator.map(_ => Future { slowFunction })

оценивается как Iterator[Future[Unit]], что является просто описанием преобразований, которые должны произойти, но еще не произошло. Ключ к пониманию:

Ленивые коллекции особенно полезны для описания последовательных операций преобразования без оценки промежуточных преобразований

Давайте немного переписаем ваш пример, чтобы подчеркните промежуточное преобразование

List(1,2,3,4)
  .iterator
  .map(_ => Future { slowFunction })         // <-- intermediate transformation
  .foreach(x => Await.result(x, 10.seconds))

Таким образом, map(_ => Future { slowFunction }) является промежуточным преобразованием, которое не оценивается, однако это преобразование, которое должно было запустить фьючерсы. Вместо этого преобразование map объединяется с преобразованием foreach, которое выполняется как одно единственное преобразование, например

 List(1,2,3,4)
   .foreach(_ => Await.result(Future { slowFunction }, 10.seconds) )

Теперь становится ясно, что мы ожидаем завершения Future, прежде чем двигаться к следующему элементу, следовательно, Future s завершено последовательно.

...