Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

blocking keyword in Scala

What's the difference between Future(blocking(blockingCall())) and blocking(Future(blockingCall()))? Both of these are defined in scala.concurrent._

I've looked at the scala docs and some other stack overflow answers but remain unclear on what the difference is.

like image 874
Manu Avatar asked Jun 24 '15 15:06

Manu


1 Answers

blocking acts as a hint to the ExecutionContext that it contains blocking code, so that it may spawn a new thread to prevent deadlocks. This presumes the ExecutionContext can do that, but not all are made to.

Let's look at each one-by-one.


Future(blocking(blockingCall()))

This requires an implicit ExecutionContext to execute the Future. If the ExecutionContext being used is a BlockContext (like scala.concurrent.ExecutionContext.Implicits.global is), it may be able to spawn a new thread in its thread pool to handle the blocking call, if it needs to. If it isn't, then nothing special happens.


blocking(Future(blockingCall()))

This tells us that Future(blockingCall()) may be a blocking call, so it is treated the same as above. Except here, Future.apply is non-blocking, so using blocking effectively does nothing but add a little overhead. It doesn't matter what ExecutionContext we're calling it from here, as it isn't blocking anyway. However, the blocking call within the Future will block a thread in the ExecutionContext it's running on, without the hint that its blocking. So, there is no reason to ever do this.

I've explained blocking more in depth in this answer.


REPL Examples:

import java.util.concurrent.Executors
import scala.concurrent._
val ec = scala.concurrent.ExecutionContext.Implicits.global
val executorService = Executors.newFixedThreadPool(4)
val ec2 = ExecutionContext.fromExecutorService(executorService)

def blockingCall(i: Int): Unit = { Thread.sleep(1000); println("blocking call.. " + i) }

// Spawns enough new threads in `ec` to handle the 100 blocking calls
(0 to 100) foreach { i => Future(blocking(blockingCall(i)))(ec) }

// Does not spawn new threads, and `ec2` reaches thread starvation
// execution will be staggered as threads are freed
(0 to 100) foreach { i => Future(blocking(blockingCall(i)))(ec2) }

// `blocking` does nothing because the `Future` is executed in a different context,
// and `ec2` reaches thread starvation
(0 to 100) foreach { i => blocking(Future(blockingCall(i))(ec2)) }

// `blocking` still does nothing, but `ec` does not know to spawn new threads (even though it could)
// so we reach thread starvation again
(0 to 100) foreach { i => blocking(Future(blockingCall(i))(ec)) }
like image 145
Michael Zajac Avatar answered Oct 23 '22 22:10

Michael Zajac