Du kennst Coroutines bereits: launch, async, suspend-Funktionen. Dieser Kurs geht einen Schritt
tiefer und schaut sich an, was darunter tatsächlich passiert — auf der JVM, mit echten Threads. Bevor
es dorthin geht, brauchst du zwei Begriffe, die im Alltag oft synonym benutzt werden, aber
unterschiedliche Dinge beschreiben: Parallelität und Nebenläufigkeit.
Parallelität (parallelism) bedeutet: Mehrere Berechnungen laufen im selben Moment wirklich gleichzeitig — das erfordert mehrere CPU-Kerne, die tatsächlich zur selben Zeit rechnen.
Nebenläufigkeit (concurrency) bedeutet etwas Schwächeres: Mehrere Berechnungen machen in überlappenden Zeiträumen Fortschritt, statt streng nacheinander abzulaufen. Das schließt echte Gleichzeitigkeit ein, verlangt sie aber nicht. Ein einzelner CPU-Kern kann nebenläufig sein, indem er schnell zwischen mehreren Aufgaben hin- und herwechselt (Interleaving) — ohne dass jemals zwei Aufgaben im exakt selben Moment aktiv sind.
Damit gilt: Jede parallele Ausführung ist auch nebenläufig, aber nicht jede nebenläufige Ausführung ist parallel. Ein Single-Core-System kann nebenläufig sein (Task-Switching), aber nie parallel.
import kotlinx.coroutines.*
fun main() = runBlocking {
repeat(4) { i ->
launch(Dispatchers.Default) {
println("Coroutine $i auf ${Thread.currentThread().name}")
}
}
}
Was hier passiert, hängt von der Hardware ab: Dispatchers.Default verteilt Arbeit auf einen
Thread-Pool, dessen Größe sich an der Anzahl verfügbarer CPU-Kerne orientiert. Auf einer
Vier-Kern-Maschine können mehrere dieser vier Coroutines tatsächlich parallel laufen. Auf einer
Maschine mit nur einem Kern laufen sie nebenläufig, aber nacheinander interleaved — nie wirklich
gleichzeitig.
Die Motivation, sich mit beidem zu beschäftigen, ist meist eine von zwei Fragen: Soll eine Aufgabe schneller werden, indem sie auf mehrere Kerne verteilt wird (Parallelität)? Oder soll eine Anwendung reaktionsfähig bleiben, während sie auf etwas Langsames wartet — eine Netzwerkantwort, eine Festplatte, einen Timer — ohne dabei blockiert zu sein (Nebenläufigkeit)? Coroutines sind in Kotlin das Werkzeug für Letzteres; ob dabei am Ende auch echte Parallelität stattfindet, hängt vom gewählten Dispatcher und der verfügbaren Hardware ab. Die folgenden Lektionen klären, was ein Dispatcher dafür konkret tut — angefangen bei dem, was er verteilt: Threads.
Lies den Code genau durch, bevor du ihn ausführst.
import kotlinx.coroutines.*
fun main() = runBlocking {
launch(Dispatchers.Default) { println("A") }
launch(Dispatchers.Default) { println("B") }
}
Gibt dieses Programm bei jedem Lauf garantiert zuerst A und dann B aus?
Ein kleiner Bestell-Service soll für jede eingehende Bestellung eine eigene Coroutine starten, die meldet, welcher Thread sie bearbeitet. Ergänze die forEach-Schleife: Starte für jede Bestellung eine Coroutine mit launch(Dispatchers.Default), die "$bestellung läuft auf ${Thread.currentThread().name}" ausgibt.
Bevor du einen Hint bekommst: Was hast du schon probiert, und woran hängt es genau?
1. Wie unterscheidet die Lektion Parallelität und Nebenläufigkeit?
2. Ein Programm mit zwei Coroutines läuft auf einer Maschine mit nur einem CPU-Kern. Ist das möglich?
3. Was war laut dieser Lektion die Motivation, überhaupt parallel oder nebenläufig zu programmieren?