Wikipedia · einfach zusammengefasst · Stand
Koroutine
In der Informatik sind Koroutinen (auch Coroutinen) eine Verallgemeinerung des Konzepts einer Prozedur oder Funktion. Der prinzipielle Unterschied zwischen …
Inhalt5 Abschnitte
Grundidee
Koroutinen, auch Coroutinen, sind in der Informatik eine Verallgemeinerung von Prozeduren oder Funktionen. Der zentrale Unterschied ist: Eine normale Prozedur läuft nach dem Aufruf bis zur Rückgabe durch, während eine Koroutine ihren Ablauf unterbrechen und später an derselben Stelle wieder aufnehmen kann. Dabei behält sie ihren Status, also zum Beispiel lokale Werte oder die aktuelle Position im Ablauf.
Koroutinen haben dadurch einen eigenen Lebenszyklus. Sie können gestartet, angehalten und fortgesetzt werden. Das ermöglicht einen hohen Grad an Nebenläufigkeit und kooperativem Multitasking: Mehrere Abläufe wechseln sich ab, indem sie freiwillig die Kontrolle abgeben. Eine Koroutine sollte dabei möglichst nicht von äußerem Zustand abhängen; ihre Funktionsparameter sollen genügen, damit die Berechnung im Funktionskörper korrekt ablaufen kann.
Frühe Programmiersprachen mit Unterstützung für Koroutinen waren Simula und Modula-2. Auch moderne Sprachen wie Python kennen ähnliche Konzepte. In Sprachen wie C oder Java ist eine direkte Implementierung schwieriger. Der Begriff stammt von Melvin Conway, der ihn 1963 in einer Veröffentlichung über Compilerbau verwendete. Donald Knuth bezeichnete Prozeduren als Spezialfall von Koroutinen.
Simulation und Implementierung
Koroutinen können auch in Sprachen simuliert werden, die sie nicht direkt unterstützen. Ein sprachunabhängiger Weg ist die Verwendung von Threads. Dabei laufen mehrere Ausführungsstränge abwechselnd und warten nach Abgabe der Kontrolle, bis sie diese wieder erhalten.
Eine andere Möglichkeit ist das sogenannte Aufrollen von Koroutinen. Dabei muss die Koroutine ihren Status selbst speichern, zum Beispiel in einer globalen Variablen. Bei jedem Aufruf springt das Programm dann an die passende Stelle im Code. Das ist jedoch nicht in jeder Sprache einfach, weil viele Programmiersprachen keinen Sprung mitten in einen Block, etwa in eine Schleife, erlauben. Solche Kontrollstrukturen müssen dann ebenfalls durch Simulationen ersetzt werden.
Python
Python unterstützt Koroutinen über die asyncio API. Seit Python 3.5 gibt es dafür die Schlüsselwörter async und await. Eine Koroutine wird mit async def definiert. await wartet auf die Fertigstellung einer Koroutine und gibt während der Wartezeit die Ausführungskontrolle zurück. Dadurch können Koroutinen nebenläufig ausgeführt werden und kooperatives Multitasking ermöglichen.
Das Artikelbeispiel startet drei Aufgaben mit asyncio.create_task und wartet mit asyncio.gather auf alle. Weil await asyncio.sleep(1) den Thread nicht blockiert, dauern die drei Wartevorgänge zusammen nur etwas über eine Sekunde.
Eine weitere Möglichkeit in Python sind Generatoren. Mit yield kann der Ablauf einer Funktion vorübergehend unterbrochen werden. Beim Aufruf einer Generator-Funktion entsteht intern ein Objekt, das den Status hält. Beim Abgeben der Kontrolle wird das vollständige Stackframe zwischengespeichert und bei Wiederaufnahme wiederhergestellt. Das Beispiel zur Fibonaccifolge erzeugt nacheinander Werte bis zu einem angegebenen limit.
In Python 2.5 wurde die Syntax von yield erweitert, um kooperatives Multitasking zu ermöglichen. In Python 3 folgten Erweiterungen zur Nutzung von Generatoren als Koroutinen, unter anderem durch async und await. In Version 3.11 wurde diese Funktionalität wieder entfernt. Generatoren mit yield bleiben Teil der Sprache; async und await sind für die asyncio API vorgesehen.
C, C++ und C#
C unterstützt weder Koroutinen noch vergleichbare Konstrukte direkt. Es gibt aber Möglichkeiten, sie zu simulieren. Eine bekannte Technik geht auf Simon Tatham zurück und ist unter anderem wegen ihrer Verwendung im SSH-Client PuTTY bekannt. Das Artikelbeispiel berechnet die Fibonaccifolge, indem der Status in Variablen first und second gespeichert wird. Da dieser Status global vorgehalten wird, kann von jeder Koroutine immer nur eine Instanz ablaufen. Durch Thread-local Storage kann der Zustand pro Thread getrennt gespeichert werden, sodass bei mehreren Threads mehrere Instanzen parallel möglich sind.
In C++ ermöglicht Boost.Coroutine, ein offizieller Bestandteil der Boost-Libraries, die Verwendung von Koroutinen. Anders als in Python ist jede Koroutine dort mit einem Stack verbunden. Dadurch sind Umschaltungen und Sprünge auch aus Unterfunktionen heraus möglich. Über Boost.Context werden ARM, MIPS, PowerPC, SPARC und X86 auf POSIX, Mac OS X und Windows unterstützt. Seit C++20 sind Koroutinen nativ in die Sprache integriert.
C# zeigt im Artikel eine koroutinenähnliche Verwendung über IEnumerable<int> und yield return. Eine Funktion fibonacci gibt dabei nacheinander Werte der Fibonaccifolge zurück, ohne alle Werte vorher in einer Liste speichern zu müssen.
Weitere Sprachen
D unterstützt Koroutinen in objektorientierter Umgebung unter dem Namen Fibers. Die Umschaltung erfolgt intern durch eine einfache Vertauschung des Stackpointers. Laut Artikel ist dies bisher nur für x86 unter Windows und Posix sowie für PowerPC verfügbar. Besonders deutlich werden Vorteile bei rekursiven Funktionen, zum Beispiel bei der Traversierung von Binärbäumen.
Tcl unterstützt Koroutinen bereits im Sprachkern und grundsätzlich plattformunabhängig. Das Beispiel erzeugt mit coroutine eine Fibonaccifunktion, die bei jedem Aufruf den nächsten Wert liefert.
PicoLisp unterstützt Koroutinen als eingebautes Sprachelement, allerdings nur in der 64-bit version. Das Beispiel definiert ebenfalls eine Fibonaccifunktion, die mit yield Werte liefert.
Kotlin nutzt im Beispiel die sequence-Funktion, um eine Koroutine zu erzeugen. Mit yield wird der Sequenz ein Wert bereitgestellt und die Koroutine unterbrochen. Durch take(7) wird sie insgesamt siebenmal unterbrochen und sechsmal fortgesetzt; ausgegeben wird [1, 1, 2, 3, 5, 8, 13].