Scala Future для понимания: последовательный против параллельного - PullRequest
0 голосов
/ 04 ноября 2018

Здесь у нас есть SeqPar объект, который содержит подпрограмму task, которая представляет собой простой макет Future, который выводит некоторую информацию об отладке и возвращает тип Future[Int].

Вопрос в том, почему experiment1 разрешено работать параллельно, а experiment2 всегда работает последовательно?

object SeqPar {
  def experiment1: Int = {
    val f1 = task(1)
    val f2 = task(2)
    val f3 = task(3)

    val computation = for {
      r1 <- f1
      r2 <- f2
      r3 <- f3
    } yield (r1 + r2 + r3)

    Await.result(computation, Duration.Inf)
  }

  def experiment2: Int = {
    val computation = for {
      r1 <- task(1)
      r2 <- task(2)
      r3 <- task(3)
    } yield (r1 + r2 + r3)

    Await.result(computation, Duration.Inf)
  }

  def task(i: Int): Future[Int] = {
    Future {
      println(s"task=$i thread=${Thread.currentThread().getId} time=${System.currentTimeMillis()}")
      i * i
    }
  }
}

Когда я запускаю experiment1, он печатает:

task=3 thread=24 time=1541326607613
task=1 thread=22 time=1541326607613
task=2 thread=21 time=1541326607613

Пока experiment2:

task=1 thread=21 time=1541326610653
task=2 thread=20 time=1541326610653
task=3 thread=21 time=1541326610654

В чем причина наблюдаемой разницы? Я действительно знаю, что for понимание нарушено, как f1.flatMap(r1 => f2.flatMap(r2 => f3.map(r3 => r1 + r2 + r3))), но я все еще упускаю момент, почему одному разрешено работать параллельно, а другому - нет.

Ответы [ 2 ]

0 голосов
/ 04 ноября 2018

Это результат действия Future(…) и flatMap:

  • val future = Future(task) запускает задачу параллельно
  • future.flatMap(result => task) организует запуск task, когда future завершает

Обратите внимание, что future.flatMap(result => task) не может запустить параллельное выполнение задачи до завершения future, потому что для запуска task нам нужен result, который доступен только после завершения future.

Теперь давайте посмотрим на ваш example1:

def experiment1: Int = {
  // construct three independent tasks and start running them
  val f1 = task(1)
  val f2 = task(2)
  val f3 = task(3)

  // construct one complicated task that is ...
  val computation =
    // ... waiting for f1 and then ...
    f1.flatMap(r1 =>
      // ... waiting for f2 and then ...
      f2.flatMap(r2 =>
        // ... waiting for f3 and then ...
        f3.map(r3 =>
          // ... adding some numbers.
          r1 + r2 + r3)))

  // now actually trigger all the waiting
  Await.result(computation, Duration.Inf)
}

Таким образом, в example1, поскольку все три задачи занимают одно и то же время и были запущены в одно и то же время, нам, вероятно, придется блокировать только при ожидании f1. Когда мы подходим к ожиданию f2, его результат уже должен быть.

Теперь, чем отличается example2? 1032 *

def experiment2: Int = {
  // construct one complicated task that is ...
  val computation =
    // ... starting task1 and then waiting for it and then ...
    task(1).flatMap(r1 =>
      // ... starting task2 and then waiting for it and then ...
      task(2).flatMap(r2 =>
        // ... starting task3 and then waiting for it and then ...
        task(3).map(r3 =>
          // ... adding some numbers.
          r1 + r2 + r3)))

  // now actually trigger all the waiting and the starting of tasks
  Await.result(computation, Duration.Inf)
}

В этом примере мы даже не создаем task(2) до того, как дождались завершения task(1), поэтому задачи не могут выполняться параллельно.

Таким образом, при программировании с помощью Scala Future вы должны контролировать свой параллелизм, правильно выбирая код типа example1 и код типа example2. Или вы можете посмотреть библиотеки, которые обеспечивают более явный контроль над параллелизмом.

0 голосов
/ 04 ноября 2018

Это потому, что Scala Futures строгие. Операция внутри Future выполняется сразу после создания Future, а затем запоминает его значение. Таким образом, вы теряете ссылочную прозрачность. В вашем случае ваши фьючерсы выполняются при первом вызове task , результат запоминается. Они не выполняются снова внутри. Во втором случае для вашего понимания создаются фьючерсы, и результат верен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...