Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JVM Concurrency auf der JavaLand am 8. März 2016

JVM Concurrency auf der JavaLand am 8. März 2016

Neue JVM-Concurrency-Modelle wie Aktoren und Verticles

Nebenläufigkeit heißt in Java: java.util.concurrent und Threads. Threads wurden ursprünglich als "lightweight processes" tituliert. Neue Prozesse zu starten, um nebenläufige Operationen zu implementieren, bedeutete zu viel Overhead und hat das Problem der Kommunikation zwischen den Prozessen aufgeworfen. Threads sollten "leicht" sein und beide Nachteile beseitigen: weniger Ressourcen für die Erzeugung und Scheduling, und gemeinsame Speichernutzung.

Allerdings stößt das Modell heute an seine Grenzen. Der Ressourcenverbrauch ist immer noch zu groß und der gemeinsame Speicher mehr Problem als Lösung: Race Conditions, Locks, Contention. Um Oracles Java-VM-Architekten John Rose zu zitieren: "Threads sind passé". Wir wollen verschiedene Ansätze betrachten, Nebenläufigkeit unterhalb des Thread-Levels umzusetzen, und ihre Vor- und Nachteile beleuchten. Vorgestellt werden Quasar Fibers, Clojure Agents, Vert.x Verticles und Akka Actors.

Lutz Hühnken

March 08, 2016
Tweet

More Decks by Lutz Hühnken

Other Decks in Programming

Transcript

  1. Wichtiger Aspekt: (blocking) I/O Wenn jeder Thread nur jeweils einen

    Task hat: (Dies ist eine Momentaufnahme, kein zeitlicher Ablauf)
  2. Niemals blockieren! Wenn ein Thread ein „Worker“ ist, der viele

    Tasks bearbeitet: (Dies ist eine Momentaufnahme, kein zeitlicher Ablauf)
  3. Effizienz-Problem: Gelöst! • Nebenläufigkeit unterhalb des Thread-Levels (Tasks) • Asynchrone

    I/O
 • Dies haben alle Modelle, die wir uns ansehen werden, gemeinsam!
 • Das haben übrigens auch alle reaktiven* Frameworks gemeinsam!
 * http://www.reactivemanifesto.org
  4. Warum reichen Threads nicht aus? Problem 1: Effizienz (bzw. Mangel

    an derselben) Problem 2: Programmiermodell
  5. They discard the most essential and appealing properties of sequential

    computation: understandability, predictability, and determinism. Threads, as a model of computation, are wildly nondeterministic, and the job of the programmer becomes one of pruning that nondeterminism.
  6. Exkurs: Die Callback-Hölle fs.readdir(source, function(err, files) { if (err) {

    console.log('Error finding files: ' + err) } else { files.forEach(function(filename, fileIndex) { console.log(filename) gm(source + filename).size(function(err, values) { if (err) { console.log('Error identifying file size: ' + err) } else { console.log(filename + ' : ' + values) aspect = (values.width / values.height) widths.forEach(function(width, widthIndex) { height = Math.round(width / aspect) console.log('resizing ' + filename + 'to ' + height + 'x' + height) this.resize(width, height).write(destination + 'w' + width + '_' + filename, function(err) { if (err) console.log('Error writing file: ' + err) }) }.bind(this)) } }) }) } })
  7. Zugegeben: Das könnte man schöner schreiben • Scala Futures /

    for-expressions • async / await (auch Scala) • JavaScript Promises (then.. then.. then)
 • All das ist „syntactic sugar“ für Callbacks - was eine gute Sache ist! • Aber lasst uns nach anderen Ansätzen für asynchrone Komposition schauen.
  8. Coroutines (Green Threads, User Mode Threads, IOC Threads, Fibers) Quasar

    Fibers http://www.paralleluniverse.co/quasar/ Channels Clojure core.async https://github.com/clojure/core.async/ Event Loop vert.x http://vertx.io Actor Model Akka http://akka.io Concurrency - Modelle
  9. new Fiber<V>() { @Override protected V run() throws SuspendExecution, InterruptedException

    { // code here } }.start(); sieht aus wie ein Thread, redet wie ein Thread
  10. class RetrieveInfo extends Fiber<Void> { ... @Override public Void run()

    { final CloseableHttpClient client = FiberHttpClientBuilder.create().build(); try { String response = client.execute(new HttpGet(MessageFormat.format(url, searchTerm)), new BasicResponseHandler()); addResult(key, StringUtils.abbreviate(response, 1000)); } catch (IOException iox) { ... Use like blocking Need to aggregate results.. „fiber blocking“ async http client
  11. private final Map<String, String> store = new ConcurrentHashMap<>(); public void

    define(final String searchTerm) { for (String currentKey : sources.keySet()) { new RetrieveInfo(currentKey, sources.get(currentKey), searchTerm).start(); } } /** * This method is working on shared mutual state - this is what we look to avoid! */ void addResult(final String key, final String result) { store.put(key, result); ... Methode, um Ergebnis entgegenzunehmen - muss „locken" - wollen wir nicht!! shared mutable state Starte Web-Aufrufe parallel im Hintergrund
  12. Fibers • Größter Vorteil: Imperative Programmierung, wie wir es von

    Threads kennen.
 • Größter Nachteil: Imperative Programmierung, wie wir es von Threads kennen!
 

  13. class FooAsync extends FiberAsync<String, FooException> implements FooCompletion { @Override public

    void success(String result) { asyncCompleted(result); } @Override public void failure(FooException exception) { asyncFailed(exception); } } String op() { new FooAsync() { protected void requestAsync() { Foo.asyncOp(this); } }.run(); } Verwandelt callback APIs in fiber blocking APIs!
  14. Fibers Zusammenfassung • Thread-artiges Programmiermodell • Nette „Tricks“: Instrumentation, Suspendable,

    Thread Interop • Fibers alleine sind ziemlich „low level“ • (Fast) 1:1 Ersatz für Threads
  15. Channels • Theoretische Basis:
 Communicating Sequential Processes (Tony Hoare 1978,

    https:// en.wikipedia.org/wiki/ Communicating_sequential_processes) • Elementarer Bestandteil von „Go“ • Unser Beispiel: Clojure core.async Channels
  16. (def echo-chan (chan)) (go (println (<! echo-chan))) (>!! echo-chan „ketchup")

    ; => true ; => ketchup go block (wie ein Fiber) >!! blocks thread <! blocks go block
  17. (def echo-buffer (chan 2)) (>!! echo-buffer "ketchup") ; => true

    (>!! echo-buffer "ketchup") ; => true (>!! echo-buffer "ketchup") ; blocks buffered channel - async
  18. (def responses-channel (chan 10)) (go (println (loop [values []] (if

    (= 3 (count values)) values (recur (conj values (<! responses-channel))))))) (defn async-get [url result] (http/get url #(go (>! result (:body %))))) (async-get "http:m-w.com..." responses-channel) (async-get "http:wiktionary.." responses-channel) (async-get "http:urbandictionary.." responses-channel) callback puts result in channel channel for responses Aggregate results recursively
  19. Channels • Erlaubt Datenfluss-orientierte Programmierung, flexible Komposition (z.B. alts!) •

    Reduktion: Immutability, sequentielles Konsumieren • go block / thread Dualität erlaubt Integration mit async und sync Libraries • Channels als Hüter des Zustandes? Channels Zusammenfassung
  20. Channels • Event loop model - Vorbild node.js • „multi

    event loop“, auf der JVM • Polyglott - Java, Scala, Clojure, JRuby, JS.. • Keine Einheiten (Verticles) deployen, die per JSON kommunizieren vert.x
  21. public class Receiver extends AbstractVerticle { @Override public void start()

    throws Exception { EventBus eb = vertx.eventBus(); eb.consumer("ping-address", message -> { System.out.println("Received message: " + message.body()); // Now send back reply message.reply("pong!"); }); System.out.println("Receiver ready!"); } } register on event bus extend Verticle connect
  22. MessageConsumer<JsonObject> consumer = eventBus.consumer("definer.task"); consumer.handler(message -> { httpClient.getNow(message.body().getString("host"), message.body().getString("path"), httpClientResponse

    -> { httpClientResponse.bodyHandler(buffer -> { eventBus.send("collector", new JsonObject() .put(...) callback based async http register send result as message
  23. Vertx vertx = Vertx.vertx(); EventBus eventBus = vertx.eventBus(); DeploymentOptions options

    = new DeploymentOptions().setInstances(sources.size()); vertx.deployVerticle("de.huehnken.concurrency.Task", options, msg -> { for (String currentKey : sources.keySet()) { eventBus.send("definer.task", new JsonObject() .put(..) which one gets the message? deploy multiple
  24. private final Map<String, String> store = new HashMap<>(); @Override public

    void start(Future<Void> startFuture) { EventBus eventBus = vertx.eventBus(); MessageConsumer<JsonObject> consumer = eventBus.consumer("collector"); consumer.handler(message -> { store.put(message.body().getString(„key"), message.body().getString("definition")); wait for results.. encapsultate state in Verticle messages are usually JSON
  25. Channels • (Sehr) lose Kopplung • Reduktion: Events und „Single

    Thread Illusion“ • Hybrides Thread-Modell erlaubt integration synchroner APIs, und dem Event-Loop Arbeit woanders abzuladen. • Zustands erfordert etwas Aufmerksamkeit • APIs sind sehr Callback-lastig, node.js inspiriert • Verteilt! Einheitliches Modell für lokale und verteilte Nebenläufigkeit! vert.x Summary
  26. Channels • The actor model [..] is a [model] that

    treats "actors" as the universal primitives of concurrent computation: in response to a message that it receives, an actor can make local decisions, create more actors, send more messages, and determine how to respond to the next message received. https://en.wikipedia.org/wiki/Actor_model • Entwickelt von Carl Hewitt 1973 • Verbreitung durch Erlang Akka
  27. class Coordinator(searchTerm: String) extends Actor { var results = Map[String,

    String]() def receive = { case StartCommand => sources foreach { case (key, url) => context.actorOf(Task.props(key, self)) ! GetDefinition(url) } context.system.scheduler.scheduleOnce(5 seconds, self, Done) case Result(key, definition) => results += key -> definition if (results.size == sources.size) { self ! Done } case Done => erzeuge Child Actors, sende Nachricht Zustand, gekapselt aggregiere Ergebnisse
  28. class Task(key: String, coordinator: ActorRef) extends Actor { def receive

    = { case GetDefinition(url) => http.singleRequest(HttpRequest(uri = url)) pipeTo self case HttpResponse(StatusCodes.OK, headers, entity, _) => coordinator ! Result(key, entity.dataBytes.utf8String) case HttpResponse(code, _, _, _) => // handle error send result to aggregator async call, translated to message
  29. Channels • Reduktion: Nachrichten und „Single Thread Illusion“ • Messages

    (as opposed to events) • „Dispatcher“ erlauben Integration synchroner APIs und das Abladen von Arbeit • pipeTo & ask zur Integration von asynchronen APIs • Verteilt! Einheitliches Modell für lokale und verteilte Nebenläufigkeit! • Supervision! Fehlerbehandlung eingebaut! Actors Summary
  30. Concurrency-Modelle Zusammenfassung Tasks (sub- thread level) Asynchronous Messaging Distribution (unified

    model) Supervision Fibers ✔ Channels (core.async) ✔ ✔ Event Bus (vert.x) ✔ ✔ ✔ Aktoren (Akka) ✔ ✔ ✔ ✔
  31. • Im Bereich Concurrency ist einiges los! • Threads are

    passé! • Sie sind die Basis auf OS- und JVM-Ebene, aber Entwickler benötigen ein anderes, besseres Modell. • Alternativen sind vorhanden, auf der JVM, hier und heute. • Wenn eure Zeit knapp ist, und ihr euch nur ein anderes Modell ansehen könnt, empfehle ich Akka. Zusammenfassung
  32. Aber ich will doch nur einen kleinen REST-Service bauen.. •

    Wenn du nicht auf dieser Ebene programmierst - kenne deine Frameworks! • Halte Ausschau nach „reactive“ oder „async“ • Play! • Akka HTTP • Red Hat vert.x • (Pivotal Reactor? Rat pack?)
  33. Achtung bei Benchmarks! • Ungeeignet: CPU-lastige Benchmarks • Ungeeignet: Alles,

    was synchrone I/O verwendet • Es geht nicht um die Geschwindigkeit bei einzelnen Requests, sondern um Skalierbarkeit (Little’s Law, L=𝛌W)
  34. (def x (agent 0)) (defn increment [c n] (+ c

    n)) (send x increment 5) ; @x -> 5 (send x increment 10) ; @x -> 15 „wrap“ a value in an agent send the code to the agent to modify value! a function
  35. Agenten • Agenten kapseln Zustand • Sende ausführbaren Code an

    Agenten, um Zustand zu ändern • Interessantes Feature, besonders in der Funktionalen Programmierung • Der Code wird asynchron ausgeführt, aber allein ist es nicht wirklich ein Concurrency-Modell
  36. Der Vollständigkeit halber • Quasar bietet auch Channels, und sogar

    Actors • Akka bietet auch Busse (lokal und verteilt), und Agenten. • Vieles stammt von außerhalb der JVM-Welt (Go Channels, node.js, Erlang Actors)
  37. Image Credits Cat on hot tin roof - from "McPhillamyActorBlog"

    - http://mcphillamy.com Agents - from "Matrix Wiki" - http://matrix.wikia.com/wiki/Agent Event Loop - http://www.freeimages.com/photo/big-looping-1522456 Channel - http://www.freeimages.com/photo/white-cliffs-of-dover-1256973 Fibers - http://www.freeimages.com/photo/fiber-optics-1503081