The documentation for scala.concurrent.ExecutionContext.Implicits.global
on the ExecutionContext trait reads:
It is possible to simply import
scala.concurrent.ExecutionContext.Implicits.global
to obtain an implicitExecutionContext
. This global context is a reasonable default thread pool
What does it mean by "reasonable default"?
An ExecutionContext can execute program logic asynchronously, typically but not necessarily on a thread pool. A general purpose ExecutionContext must be asynchronous in executing any Runnable that is passed into its execute -method.
Future represents a result of an asynchronous computation that may or may not be available yet. When we create a new Future, Scala spawns a new thread and executes its code. Once the execution is finished, the result of the computation (value or exception) will be assigned to the Future.
Default
It's a fixed size ThreadPool, which has as many threads as the processors on the machine. A reasonable default means it's good for most things most of the time.
What a "good" thread pool is
First, it's important to understand you only have as many threads as the cores on the machine. All other threads are so called demon threads and it's all about being smart with queue-ing and executing.(at language/library level).
CachedThreadPool: Many short lived/cheap tasks
The type of thread pools you spawn vastly depend on the actions they are mean to perform. For a lot of short lived actions(say database queries), you would go a with a cached thread pool.
Because each individual task is relatively cheap but spawning a new thread is expensive, you are better off with a CachedThreadPool.
FixedThreadPool: Long running/expensive tasks
In contrast with the above, for very expensive operations, you probably want to limit the amount of threads running at one time, for various reasons: memory, performance etc.
ForkJoinPool: Divide et impera
This type of pool is useful when you need to perform a very large computation, but you can divide it into smaller bits individual workers can compute.
The list goes on and on. Bottom line, Scala gives you something in between all of the above. Specifically, Scala tries to create a ForkJoinPool and defaults to a ThreadPoolExecutor if the first fails.
try {
new ForkJoinPool(
desiredParallelism,
threadFactory,
uncaughtExceptionHandler,
true) // Async all the way baby
} catch {
case NonFatal(t) =>
System.err.println("Failed to create ForkJoinPool for the default ExecutionContext, falling back to ThreadPoolExecutor")
t.printStackTrace(System.err)
val exec = new ThreadPoolExecutor(
desiredParallelism,
desiredParallelism,
5L,
TimeUnit.MINUTES,
new LinkedBlockingQueue[Runnable],
threadFactory
)
exec.allowCoreThreadTimeOut(true)
exec
}
}
The full list here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With