The movies example revisited

Still within our imminent-playground project, open the src/imminent_playground/core.clj file and add the appropriate definitions:

(ns imminent-playground.core
  (:require [clojure.pprint :refer [pprint]]
            [imminent.core :as i]))

(def movie ...)

(def actor-movies ...)

(def actor-spouse ...)

(def top-5-movies ...)

We will be using the same data as in the previous program, represented in the preceding snippet by the use of ellipses. Simply copy the relevant declarations over.

The service functions will need small tweaks in this new version:

(defn cast-by-movie [name]
  (i/future (do (Thread/sleep 5000)
                (:cast  movie))))

(defn movies-by-actor [name]
  (i/future (do (Thread/sleep 2000)
                (->> actor-movies
                     (filter #(= name (:name %)))
                     first))))

(defn spouse-of [name]
  (i/future (do (Thread/sleep 2000)
                (->> actor-spouse
                     (filter #(= name (:name %)))
                     first))))

(defn top-5 []
  (i/future (do (Thread/sleep 5000)
                top-5-movies)))

(defn aggregate-actor-data [spouses movies top-5]
    ...)

The main difference is that all of them now return an imminent future. The aggregate-actor-data function is also the same as before.

This brings us to the -main function, which was rewritten to use imminent combinators:

(defn -main [& args]
  (time (let [cast    (cast-by-movie "Lord of The Rings: The Fellowship of The Ring")
              movies  (i/flatmap cast #(i/map-future movies-by-actor %))
              spouses (i/flatmap cast #(i/map-future spouse-of %))
              result  (i/sequence [spouses movies (top-5)])]
          (prn "Fetching data...")
          (pprint (apply aggregate-actor-data
                      (i/dderef (i/await result)))))))

The function starts much like its previous version, and even the first binding, cast, looks familiar. Next we have movies, which is obtained by fetching an actor's movies in parallel. This in itself returns a future, so we flatmap it over the cast future to obtain our final result:

movies  (i/flatmap cast #(i/map-future movies-by-actor %))

spouses works in exactly the same way as movies, which brings us to result. This is where we would like to bring all asynchronous computations together. Therefore, we use the sequence combinator:

result  (i/sequence [spouses movies (top-5)])

Finally, we decide to block on the result future—by using await—so we can print the final result:

(pprint (apply aggregate-actor-data
                      (i/dderef (i/await result)))

We run the program in the same way as before, so simply type the following in the command line, under the project's root directory:

lein run -m imminent-playground.core
"Fetching data..."
({:name "Cate Blanchett",
  :spouse "Andrew Upton",
  :movies
  ("Lord of The Rings: The Fellowship of The Ring - (top 5)"
   "Lord of The Rings: The Return of The King"
   "The Curious Case of Benjamin Button")}
...
"Elapsed time: 7088.398 msecs"

The result output was trimmed as it is exactly the same as before, but two things are different and deserve attention:

  • The first output, Fetching data..., is printed to the screen a lot faster than in the example using Clojure futures
  • The overall time it took to fetch all that is shorter, clocking in at just over 7 seconds

This highlights the asynchronous nature of imminent futures and combinators. The only time we had to wait is when we explicitly called await at the end of the program.

More specifically, the performance boost comes from the following section in the code:

(let [...
      result  (i/sequence [spouses movies (top-5)])]
   ...)

Because none of the previous bindings block the current thread, we never have to wait to kick off top-5 in parallel, shaving off roughly 3 seconds from the overall execution time. We didn't have to explicitly think about the order of execution—the combinators simply did the right thing.

Finally, one last difference is that we didn't have to explicitly call shutdown-agents as before. The reason for this is that imminent uses a different type of thread pool: a ForkJoinPool (see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ForkJoinPool.html).

This pool has a number of advantages—each with its own trade-off—over the other thread pools, and one characteristic is that we don't need to explicitly shut it down—all threads it creates daemon threads.

When the JVM shuts down, it hangs waiting for all non-daemon threads to finish. Only then does it exit. That's why using Clojure futures would cause the JVM to hang, if we had not called shutdown-agents.

All threads created by the ForkJoinPool are set as daemon threads by default: when the JVM attempts to shut down, and if the only threads running are daemon ones, they are abandoned and the JVM exits gracefully.

Combinators such as map and flatmap, as well as the functions sequence and map-future, aren't exclusive to futures. They have many more fundamental principles by which they abide, making them useful in a range of domains. Understanding these principles isn't necessary for following the contents of this book. Should you want to know more about these principles, please refer to the Appendix , The Algebra of Library Design.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.99.71