Last active
December 11, 2015 21:18
-
-
Save mariusae/4660893 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Concurrent Programming with Futures | |
| =================================== | |
| A *Future* is a placeholder for the result of an asynchronous | |
| operation. Examples of such operations are: | |
| - an RPC to a remote host | |
| - a long computation in another thread | |
| - reading from disk | |
| Note that these operations are fallible: remote hosts could crash, | |
| computations might throw an exception, disks could fail, etc. etc. | |
| A `Future[T]`, then, occupies exactly one of three states: | |
| - Empty (pending) | |
| - Succeeded (with a result of type `T`) | |
| - Failed (with a `Throwable`) | |
| It is possible to directly query a Future for its state via its `poll` method, | |
| but this is typically not useful. Instead, a callback may be registered to | |
| receive the results once they are made available: | |
| :: | |
| val f: Future[Int] | |
| f onSuccess { res => | |
| println("The result is " + res) | |
| } | |
| which will be invoked only on success. Callbacks may also be registered | |
| to account for failures: | |
| :: | |
| f onFailure { cause: Throwable => | |
| println("f failed with " + cause) | |
| } | |
| This is more useful, but still cumbersome. The power of Futures lie in | |
| how they *compose*. Most operations can be broken up into smaller | |
| operations which in turn constitute the *composite operation*. Futures | |
| makes it easy to create such composite operations. | |
| Consider the simple example of updating a value in a database. This | |
| typically involves | |
| 1. Checking the current value | |
| 2. Computing the next value | |
| 3. Setting the new value | |
| This is an example of *sequential* composition: in order to do the | |
| next step, we must have succesfully completed the previous one. With | |
| Futures, this is called `flatMap`. The result of `flatMap` is a Future | |
| representing the result of this composite operation. | |
| :: | |
| def getValue(key: String): Future[Value] | |
| def setValue(key: String, value: Value): Future[Unit] | |
| def computeNextValue(value: Value): Value | |
| val key = "somekey" | |
| val f: Future[Unit] = getValue(key) flatMap { value => | |
| val nextValue = computeNextValue(value) | |
| setValue(key, nextValue) | |
| } | |
| f onSuccess { _: Unit => | |
| println("Successfully completed the update") | |
| } | |
| `f` represents the *composite* operation. It is the result of first | |
| retrieving the current value, computing the next value and setting | |
| that value. If either of the smaller operations fail | |
| (`getCurrentValue`, `setCurrentValue`), the composite operation also | |
| fails. | |
| The astute reader may have noticed something peculiar: this is | |
| typically the job of the semicolon! That is not far from the truth: | |
| semicolons sequence two statements, and with traditional I/O | |
| operations, have the same effect as `flatMap` does above (the | |
| exception mechanism takes the role of a failed future). Futures | |
| are much more versatile, however, as we'll see. | |
| It is also possible to compose futures *concurrently*. We can extend | |
| our above example to demonstrate: let's say that in order to compute | |
| the next value, we needed a number of values from different sources. A | |
| common real-world example of this is to create a templatized web page: | |
| you often need bits and pieces of data from various sources | |
| (databases, caches, etc.) in order to output the final webpage. | |
| Concurrent composition is provided by `Future.join`: | |
| :: | |
| def computeCompositeValue(vs: Value*): Value = ... | |
| val fs: Seq[Future[Value]] = Seq( | |
| getValue("key1"), | |
| getValue("key2"), | |
| getValue("key3")) | |
| val joined: Future[(Value, Value, Value)] = Future.join(fs:_*) | |
| val f: Future[Unit] = joined flatMap { case (v1, v2, v3) => | |
| val compositeValue = computeCompositeValue(v1, v2, v3) | |
| setValue("compositeKey", compositeValue) | |
| } | |
| Here we have combined both concurrent and sequential composition: | |
| first we gather the values for the three keys, then we set the | |
| composite value. That is, future `f` is the result of the three `get` | |
| operations executing concurrently then, when they are all done, | |
| computing the composite value and setting it in our data store. | |
| As with sequential composition, concurrent composition also propagates | |
| failures: the future `joined` will fail if any of the underlying | |
| futures do. | |
| Futures are also flexible enough to write your own combinators: this | |
| is quite useful, and gives rise to a great amount of modularity in | |
| distributed systems as common patterns can be cleanly abstracted. | |
| Other resources | |
| --------------- | |
| `Effective Scala`_ contains a `section discussing futures`_ | |
| As of Scala 2.10, the Scala standard library has its own futures | |
| implementation and API, described here_. Note that | |
| this is largely similar to the API used in Finagle | |
| (*com.twitter.util.Future*), but there are still some naming | |
| differences. | |
| Akka_ also has a `section dedicated to futures`_. | |
| .. _Akka: http://akka.io/ | |
| .. _`Effective Scala`: <http://twitter.github.com/effectivescala/>`_ | |
| .. _`section discussing futures`: http://twitter.github.com/effectivescala/#Twitter's%20standard%20libraries-Futures | |
| .. _here: http://docs.scala-lang.org/overviews/core/futures.html | |
| .. _`section dedicated to futures`: http://doc.akka.io/docs/akka/2.1.0/scala/futures.html |
Author
4: i've reworded this
18: this is a well-defined concept in Scala and Java
43: i'd like to avoid comparison for now--just explain the facts
50: fixed
64: fixed by adding signatures
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
4: executing concurrently and hence available later (or something like it) - might not be clear concurrently == not available at the place of creation. If I remember correctly, finagle readme has this explained nicely.
18: either link to or explain a bit what is Throwable since this is first mention of it
43: maybe add: which is one of main advantages over async systems based on callbacks only (like Java?)
50: "setting a new value" should be enough?
64: you might want to clarify that computeNextValue is not async (otherwise you'd need two flatMaps). It's position in the numbered list above kind of implies all three operations are equal.