From 6c22f79d9735f39dad6f2723b8b9748b70a47d52 Mon Sep 17 00:00:00 2001 From: Bruno Bieth Date: Wed, 19 Feb 2014 10:34:00 +0100 Subject: [PATCH 1/2] execution context --- overviews/core/_posts/2012-09-20-futures.md | 134 +++++++++++++++++++- 1 file changed, 131 insertions(+), 3 deletions(-) diff --git a/overviews/core/_posts/2012-09-20-futures.md b/overviews/core/_posts/2012-09-20-futures.md index c714381871..294ec84f9c 100644 --- a/overviews/core/_posts/2012-09-20-futures.md +++ b/overviews/core/_posts/2012-09-20-futures.md @@ -13,9 +13,9 @@ languages: [ja] Futures provide a nice way to reason about performing many operations in parallel-- in an efficient and non-blocking way. The idea -is simple, a `Future` is a sort of a placeholder object that you can -create for a result that does not yet exist. Generally, the result of -the `Future` is computed concurrently and can be later collected. +is simple, a [Future](http://www.scala-lang.org/api/current/index.html#scala.concurrent.Future) +is a sort of a placeholder object that you can create for a result that does not yet exist. +Generally, the result of the `Future` is computed concurrently and can be later collected. Composing concurrent tasks in this way tends to result in faster, asynchronous, non-blocking parallel code. By default, futures and promises are non-blocking, making use of @@ -36,6 +36,134 @@ in the case of an application which requires blocking futures, for an underlying environment to resize itself if necessary to guarantee progress. --> +A typical future look like this: + + val inverseFuture : Future[Matrix] = Future { + fatMatrix.inverse() // non-blocking long lasting computation + }(executionContext) + + +Or with the more idiomatic + + implicit val ec : ExecutionContext = ... + val inverseFuture : Future[Matrix] = Future { + fatMatrix.inverse() + } // ec is implicitly passed + + +Both code snippets delegate the execution of `fatMatrix.inverse()` to an `ExecutionContext` and embody the result of the computation in `inverseFuture`. + + +## Execution Context + +Future and Promises revolve around [ExecutionContexts](http://www.scala-lang.org/api/current/index.html#scala.concurrent.ExecutionContext), responsible for executing computations. + +An `ExecutionContext` is similar to an [Executor](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executor.html): it is free to execute computations in a new thread, in a pooled thread or in the current thread (although this is discouraged - more on that below). + +The `scala.concurrent` package comes out of the box with an `ExecutionContext` implementation, a global static thread pool. +It is also possible to adapt an `Executor` into an `ExecutionContext`, and you are of course free to implement your own. + + +### The Global Execution Context + +`ExecutionContext.global` is an `ExecutionContext` backed by a [ForkJoinPool](http://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html). It should be sufficient for most situations but requires some care. +A `ForkJoinPool` manages a limited amount of threads (the maximum amount of thread being refered to as parallelism level). That limit can be exceeded in the presence of blocking computation (the pool must be notified though). + +By default the `ExecutionContext.global` set the parallelism level of its underlying fork-join pool to the amount of available processors ([Runtime.availableProcessors](http://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html#availableProcessors%28%29)). +This configuration can be overriden by setting one (or more) of the following VM attribute: + + * scala.concurrent.context.minThreads - defaults to `Runtime.availableProcessors` + * scala.concurrent.context.numThreads - can be a number or a multiplier (N) in the form 'xN' ; defaults to `Runtime.availableProcessors` + * scala.concurrent.context.maxThreads - defaults to `Runtime.availableProcessors` + +The parallelism level will be set to `numThreads` as long as it remains within `[minThreads; maxThreads]`. + +As stated above the `ForkJoinPool` can increase the amount of threads beyond its `parallelismLevel` in the presence of blocking computation. As explained in the `ForkJoinPool` API, this is only possible if the pool is explicitly notified: + + import scala.concurrent.Future + import scala.concurrent.forkjoin._ + + // the following is equivalent to `implicit val ec = ExecutionContext.global` + import ExecutionContext.Implicits.global + + Future { + ForkJoinPool.managedBlock( + new ManagedBlocker { + var done = false + + def block(): Boolean = { + try { + myLock.lock() + // ... + } finally { + done = true + } + true + } + + def isReleasable: Boolean = done + } + ) + } + + +Fortunately the concurrent package provides a convenient way for doing so: + + import scala.concurrent.Future + import scala.concurrent.blocking + + Future { + blocking { + myLock.lock() + // ... + } + } + +Note that `blocking` is a general construct that will be discussed more in depth [below](#in_a_future). + +Last but not least, you must remember that the `ForkJoinPool` isn't designed for long lasting blocking operations. Even when notified with `blocking` the pool might not spawn new workers as you would expect, and when new workers are created they can be as many as 32767. +To give you an idea, the following code will use 32000 threads: + + implicit val ec = ExecutionContext.global + + for( i <- 1 to 32000 ) { + Future { + blocking { + Thread.sleep(999999) + } + } + } + + +If you need to wrap long lasting blocking operations we recommend using a dedicated `ExecutionContext`, for instance by wrapping a Java `Executor`. + + +### Adapting Java Executor + +Using `ExecutionContext.fromExecutor` you can adapt a Java `Executor` into an `ExecutionContext`. +For instance: + + ExecutionContext.fromExecutor(new ThreadPoolExecutor( /* your configuration */ )) + + +### Synchronous Execution Context + +One might be tempted to have an `ExecutionContext` that runs computations within the current thread: + + val currentThreadExecutionContext = ExecutionContext.fromExecutor( + new Executor { + def execute(runnable: Runnable) { runnable.run() } + }) + +This should be avoided as it introduce non-determinism in the execution of your future. + + Future { doSomething }(ExecutionContext.global).map { doSomethingElse }(currentThreadExecutionContext) + +`doSomethingElse` might either execute in `doSomething`'s thread or in the main thread, and therefore be either asynchronous or synchronous. +As explained [here](http://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/) a callback shouldn't be both. + + + ## Futures A `Future` is an object holding a value which may become available at some point. From c346d1ee43e37396998ac0235a3be4f8c119e95b Mon Sep 17 00:00:00 2001 From: Bruno Bieth Date: Tue, 8 Sep 2015 08:39:45 +0200 Subject: [PATCH 2/2] address PR comments --- overviews/core/_posts/2012-09-20-futures.md | 66 +++++++++++++-------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/overviews/core/_posts/2012-09-20-futures.md b/overviews/core/_posts/2012-09-20-futures.md index 294ec84f9c..9a9a21074c 100644 --- a/overviews/core/_posts/2012-09-20-futures.md +++ b/overviews/core/_posts/2012-09-20-futures.md @@ -11,11 +11,11 @@ languages: [ja] ## Introduction -Futures provide a nice way to reason about performing many operations -in parallel-- in an efficient and non-blocking way. The idea -is simple, a [Future](http://www.scala-lang.org/api/current/index.html#scala.concurrent.Future) -is a sort of a placeholder object that you can create for a result that does not yet exist. -Generally, the result of the `Future` is computed concurrently and can be later collected. +Futures provide a way to reason about performing many operations +in parallel-- in an efficient and non-blocking way. +A [`Future`](http://www.scala-lang.org/api/current/index.html#scala.concurrent.Future) +is a placeholder object for a value that may not yet exist. +Generally, the value of the Future is supplied concurrently and can subsequently be used. Composing concurrent tasks in this way tends to result in faster, asynchronous, non-blocking parallel code. By default, futures and promises are non-blocking, making use of @@ -36,16 +36,16 @@ in the case of an application which requires blocking futures, for an underlying environment to resize itself if necessary to guarantee progress. --> -A typical future look like this: +A typical future looks like this: - val inverseFuture : Future[Matrix] = Future { + val inverseFuture: Future[Matrix] = Future { fatMatrix.inverse() // non-blocking long lasting computation }(executionContext) -Or with the more idiomatic +Or with the more idiomatic: - implicit val ec : ExecutionContext = ... + implicit val ec: ExecutionContext = ... val inverseFuture : Future[Matrix] = Future { fatMatrix.inverse() } // ec is implicitly passed @@ -56,21 +56,31 @@ Both code snippets delegate the execution of `fatMatrix.inverse()` to an `Execut ## Execution Context -Future and Promises revolve around [ExecutionContexts](http://www.scala-lang.org/api/current/index.html#scala.concurrent.ExecutionContext), responsible for executing computations. +Future and Promises revolve around [`ExecutionContext`s](http://www.scala-lang.org/api/current/index.html#scala.concurrent.ExecutionContext), responsible for executing computations. -An `ExecutionContext` is similar to an [Executor](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executor.html): it is free to execute computations in a new thread, in a pooled thread or in the current thread (although this is discouraged - more on that below). +An `ExecutionContext` is similar to an [Executor](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executor.html): +it is free to execute computations in a new thread, in a pooled thread or in the current thread +(although executing the computation in the current thread is discouraged -- more on that below). The `scala.concurrent` package comes out of the box with an `ExecutionContext` implementation, a global static thread pool. -It is also possible to adapt an `Executor` into an `ExecutionContext`, and you are of course free to implement your own. +It is also possible to convert an `Executor` into an `ExecutionContext`. +Finally, users are free to extend the `ExecutionContext` trait to implement their own execution contexts, +although this should only be done in rare cases. ### The Global Execution Context -`ExecutionContext.global` is an `ExecutionContext` backed by a [ForkJoinPool](http://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html). It should be sufficient for most situations but requires some care. -A `ForkJoinPool` manages a limited amount of threads (the maximum amount of thread being refered to as parallelism level). That limit can be exceeded in the presence of blocking computation (the pool must be notified though). +`ExecutionContext.global` is an `ExecutionContext` backed by a [ForkJoinPool](http://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html). +It should be sufficient for most situations but requires some care. +A `ForkJoinPool` manages a limited amount of threads (the maximum amount of thread being referred to as *parallelism level*). +The number of concurrently blocking computations can exceed the parallelism level +only if each blocking call is wrapped inside a `blocking` call (more on that below). +Otherwise, there is a risk that the thread pool in the global execution context is starved, +and no computation can proceed. -By default the `ExecutionContext.global` set the parallelism level of its underlying fork-join pool to the amount of available processors ([Runtime.availableProcessors](http://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html#availableProcessors%28%29)). -This configuration can be overriden by setting one (or more) of the following VM attribute: +By default the `ExecutionContext.global` sets the parallelism level of its underlying fork-join pool to the amount of available processors +([Runtime.availableProcessors](http://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html#availableProcessors%28%29)). +This configuration can be overriden by setting one (or more) of the following VM attributes: * scala.concurrent.context.minThreads - defaults to `Runtime.availableProcessors` * scala.concurrent.context.numThreads - can be a number or a multiplier (N) in the form 'xN' ; defaults to `Runtime.availableProcessors` @@ -78,7 +88,8 @@ This configuration can be overriden by setting one (or more) of the following VM The parallelism level will be set to `numThreads` as long as it remains within `[minThreads; maxThreads]`. -As stated above the `ForkJoinPool` can increase the amount of threads beyond its `parallelismLevel` in the presence of blocking computation. As explained in the `ForkJoinPool` API, this is only possible if the pool is explicitly notified: +As stated above the `ForkJoinPool` can increase the amount of threads beyond its `parallelismLevel` in the presence of blocking computation. +As explained in the `ForkJoinPool` API, this is only possible if the pool is explicitly notified: import scala.concurrent.Future import scala.concurrent.forkjoin._ @@ -121,7 +132,9 @@ Fortunately the concurrent package provides a convenient way for doing so: Note that `blocking` is a general construct that will be discussed more in depth [below](#in_a_future). -Last but not least, you must remember that the `ForkJoinPool` isn't designed for long lasting blocking operations. Even when notified with `blocking` the pool might not spawn new workers as you would expect, and when new workers are created they can be as many as 32767. +Last but not least, you must remember that the `ForkJoinPool` is not designed for long lasting blocking operations. +Even when notified with `blocking` the pool might not spawn new workers as you would expect, +and when new workers are created they can be as many as 32767. To give you an idea, the following code will use 32000 threads: implicit val ec = ExecutionContext.global @@ -138,9 +151,9 @@ To give you an idea, the following code will use 32000 threads: If you need to wrap long lasting blocking operations we recommend using a dedicated `ExecutionContext`, for instance by wrapping a Java `Executor`. -### Adapting Java Executor +### Adapting a Java Executor -Using `ExecutionContext.fromExecutor` you can adapt a Java `Executor` into an `ExecutionContext`. +Using the `ExecutionContext.fromExecutor` method you can wrap a Java `Executor` into an `ExecutionContext`. For instance: ExecutionContext.fromExecutor(new ThreadPoolExecutor( /* your configuration */ )) @@ -152,15 +165,20 @@ One might be tempted to have an `ExecutionContext` that runs computations within val currentThreadExecutionContext = ExecutionContext.fromExecutor( new Executor { + // Do not do this! def execute(runnable: Runnable) { runnable.run() } }) -This should be avoided as it introduce non-determinism in the execution of your future. +This should be avoided as it introduces non-determinism in the execution of your future. - Future { doSomething }(ExecutionContext.global).map { doSomethingElse }(currentThreadExecutionContext) + Future { + doSomething + }(ExecutionContext.global).map { + doSomethingElse + }(currentThreadExecutionContext) -`doSomethingElse` might either execute in `doSomething`'s thread or in the main thread, and therefore be either asynchronous or synchronous. -As explained [here](http://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/) a callback shouldn't be both. +The `doSomethingElse` call might either execute in `doSomething`'s thread or in the main thread, and therefore be either asynchronous or synchronous. +As explained [here](http://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/) a callback should not be both.