Leseproben
René Preißel Selbstständig

„Mit Koroutinen ist es möglich, klassischen sequenziellen Code zu schreiben und trotzdem Threads nicht unnötig zu blockieren. Im Sinne der Ressourcennutzung sind Koroutinen mit Callbacks und reaktiven Streams gleichauf. Koroutinen sind „sequenziell per Default“, und Nebenläufigkeit muss explizit eingebaut werden. Das Konzept suspend ermöglicht es, viele asynchrone Konzepte als Bibliothek zu bauen, ohne die Sprache erweitern zu müssen.“

Kotlin ist aus der Android-Entwicklung nicht mehr wegzudenken, und auch im Backend nimmt es Fahrt auf. Neben der kompakten Kotlin-Syntax versprechen Koroutinen, das Entwicklerleben zu vereinfachen. Sie ermöglichen es, nichtblockierende Anwendungen zu schreiben, ohne mit dem gewohnten Programmierparadigma zu brechen.

Die Beispiele für diesen Artikel sind – wenig überraschend – mit Kotlin geschrieben. Im Kasten „Kotlin kompakt“ sind die wichtigsten syntaktischen Unterschiede zu Java zusammengefasst, und auf Kotlinlang.org findet sich eine gute Referenz für den tieferen Einstieg in die Sprache.

Was ist eigentlich das Problem?

Damit die nachfolgende Diskussion nicht anhand von abstrakten Beispielen stattfindet, starten wir mit einem konkreten Szenario: Wir wollen einen Web Service für die Erstellung von Bildercollagen entwickeln. Mit Hilfe einer Suchmaschine werden mehrere Bilder für einen wählbaren Begriff ermittelt und zu einem neuen Bild zusammengefügt. Der Ablauf wird wie folgt aussehen:

  1. Ermitteln von mehreren Bild-URLs über eine Suchmaschine
  2. Laden der einzelnen Bilder
  3. Kombinieren der Bilder zu einer Collage

Eine klassische Umsetzung könnte wie in Listing 1 aussehen, die einzelnen Schritte sind gut zu erkennen.

fun createCollage(query: String, count: Int): BufferedImage {
  val urls = requestImageUrls(query, count)
  val images = urls.map { requestImageData(it) }
  return combineImages(images)
}

Nachdem diese Version in Betrieb genommen wurde und der Service immer beliebter wird, bemerken wir jedoch ein Problem mit der Skalierbarkeit. Unter Last werden einzelne Clients abgewiesen; und das, obwohl ein großer Thread-Pool konfiguriert wurde. Ein Blick ins Monitoring offenbart kaum CPU-Auslastung, dafür ist der Speicherbedarf unserer Anwendung erstaunlich hoch.

Vermutlich ist vielen Lesern das Problem bereits offensichtlich. Der Service tut selbst sehr wenig, er wartet die meiste Zeit auf Antworten von anderen Servern. Abbildung 1 veranschaulicht das Problem.

Abb. 1: Blockierende Threads bei synchronen Remote-Aufrufen

Abb. 1: Blockierende Threads bei synchronen Remote-Aufrufen

Die Funktion createCollage ruft für jedes Bild die Funktion requestImageData auf. Diese nutzt eine blockierende, synchrone HTTP-Bibliothek, um die Bilder zu laden. Bei vielen parallelen Clientzugriffen werden somit alle Threads aus dem Pool benutzt. Jeder Thread wartet auf den Download der Bilder und belegt Speicher für den eigenen, pausierten Aufrufstack. Neue Clients erhalten irgendwann keinen Thread mehr und müssen aufgeben.

Den vollständigen Artikel lesen Sie in der Ausgabe:

Java Magazin 5.19 - "Java 12"

Alle Infos zum Heft
579886728Koroutinen in Kotlin – eine Einführung
X
- Gib Deinen Standort ein -
- or -