K.Chen
K.Chen

Reputation: 1286

future built from by-name parameter not executed in parallel

I am trying to build a new control structure which create a thread for each of its argument, and run them in parallel. The code seems to be fine when I built two future manually for each input, because I see the fast thread finishes before the slow thread.

Here is output:

fast
slow

However, if I use List(a, b).map(f => Future {f}) then I always see fast thread is executed after slow is done. Here is the output:

slow
fast

Can someone explain this?

code pasted here:

import java.util.concurrent.Executors

import scala.concurrent.{ExecutionContext, Future}

object ExecInParallel extends App {

  def run(a: => Unit, b: => Unit): Unit = {

    val executorService = Executors.newFixedThreadPool(2)
    implicit val executionContext = 
    ExecutionContext.fromExecutorService(executorService)

    // af and bf are executed in parallel
    val af = Future(a)
    val bf = Future(b)

    // however, the following code is not parallel
    List(a, b).map(f => Future(f))

    Thread.sleep(3000)
    executorService.shutdown
  }

  run(
    {
      Thread.sleep(2000)
      println("slow")
    }, 
    {
      Thread.sleep(1000)
      println("fast")
    }
  )
}

Upvotes: 0

Views: 69

Answers (3)

PH88
PH88

Reputation: 1806

Your by-name a and b is executed (sequentially) in List(a, b), before the construction of Future in map. If you check the inferred type of List(a, b) you'd see it's a List[Unit].

To achieve what you intented, you need a list of functions rather than list of results.

The following will work:

List(a _, b _).map(f => Future(f()))

Upvotes: 1

pedrofurla
pedrofurla

Reputation: 12783

That's because a and b are evaluated every time they are referenced in a non-by-name position and List(a, b) arguments are not by-name. From https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_name:

Call by name is an evaluation strategy where the arguments to a function are not evaluated before the function is called—rather,... then left to be evaluated whenever they appear in the function. If an argument is not used in the function body, the argument is never evaluated; if it is used several times, it is re-evaluated each time it appears.

Effectively that is equivalent to this code:

List({
  Thread.sleep(2000)
  println("slow")
  }, 
  {
    Thread.sleep(1000)
    println("fast")
  }).map(f => Future(f))

Since the List constructor doesn't take by-name arguments, these values are evaluated before the list itself is even constructed

Upvotes: 1

Nyavro
Nyavro

Reputation: 8866

This happens because you first create list of two call-by name values:

List(a, b)...

and until a and b not fully computed the map operation is not executed. When List(a,b) is ready you wrap it in Futures:

List(a, b).map(f => Future(f)) 

Upvotes: 1

Related Questions