Wechselgesang

Moderne Programmiersprachen wollen es dem Anwender in der Regel leichter machen, Programme zu erstellen. Dafür greifen sie mitunter auch auf uralte Konzepte wie Koroutinen zurück.

In Pocket speichern vorlesen Druckansicht 1 Kommentar lesen
Lesezeit: 4 Min.
Von
  • Michael Riepe

Nahezu jede Programmiersprache kennt Subroutinen. Sogar viele Assembler-Dialekte bieten mit Befehlen wie call und return alles Nötige zum Bau von Prozeduren und Funktionen, sodass der Entwickler häufiger genutzte Befehlsfolgen nur einmal schreiben muss und anschließend bei Bedarf aufrufen kann. Das erleichtert ihm nicht nur die Arbeit, sondern erhöht außerdem die Lesbarkeit seines Programmcodes – sofern die Subroutinen aussagefähige Namen besitzen.

Laut Donald Knuth sind Subroutinen jedoch nur Spezialfälle eines generelleren Konstrukts: der Koroutinen. Obwohl die seit 50 Jahren bekannt sind – Melvin Conway erfand den Begriff 1963 –, gab es lange Zeit nur wenige Programmiersprachen, die das Konzept umsetzten; zu den bekannteren gehört Niklaus Wirths Modula-2. Erst in jüngerer Zeit scheinen sie stärker in Mode zu kommen: In der einen oder anderen Form findet man sie etwa in C#, Go, JavaScript, Lua, Python oder Ruby.

Außer call und return kennen Koroutinen zwei weitere Grundoperationen. Mit yield gibt eine Koroutine die Kontrolle zeitweilig an den Aufrufer zurück; in manchen Sprachen kann sie wie beim return einen Wert übergeben. resume setzt eine Koroutine dort fort, wo sie angehalten hat.

Derlei kann man natürlich auch mit Threads bewerkstelligen. Allerdings sind Threads relativ schwergewichtig – ein Kontextwechsel von einem Thread zum anderen kostet in der Regel deutlich mehr Zeit als das Umschalten zwischen zwei Koroutinen. Andererseits bieten Koroutinen keine Nebenläufigkeit – es ist immer nur eine aktiv. Doch in vielen Fällen ist das gar nicht nötig, weil der Aufrufer ohnehin warten muss, bis die Koroutine mit ihrer Arbeit fertig ist. Da es nicht zu Konflikten zwischen gleichzeitig ausgeführten Programmteilen kommen kann, sind Koroutinen einfacher zu handhaben als Threads.

Mehr Infos

Listing: Ein Iterator als Koroutine

 class Element;

class List {
List *next;
Element *element;
};

Element*
iterator(List *list) {
while (list) {
yield list->element;
list = list->next;
}
return 0;
}

Dass Generationen von Programmierern ohne Koroutinen ausgekommen sind, legt den Schluss nahe, dass man sie nicht unbedingt benötigt. Vom technischen Standpunkt gesehen ist das sicher richtig, nicht jedoch vom ästhetischen oder praktischen. Manche Aufgaben lassen sich mit Koroutinen erheblich eleganter und einfacher lösen als mit anderen Mitteln. Ein Iterator etwa, der eine einfach verkettete Liste abklappert und nacheinander ihre Elemente zurückgibt, könnte in einer hypothetischen, an C++ angelehnten Sprache mit Koroutinen so aussehen wie im Listing gezeigt. Wem das Beispiel zu trivial erscheint, der überlege sich, wie ein Iterator für einen Binärbaum mit und ohne Koroutinen aussehen könnte.

Wessen bevorzugte Sprache nicht mit Koroutinen gesegnet ist, der kann sich nach einer geeigneten Bibliothek umsehen oder selbst zum Werkzeugkasten greifen. Einen cleveren, aber nicht ungefährlichen Weg, Koroutinen in C mithilfe des Präprozessors zu simulieren, demonstriert Simon Tatham auf seiner Webseite (siehe „Alle Links“ [a]). Ohne solche syntaktischen Tricks kommt die Portable Coroutine Library von Davide Libenzi aus [b]. Als Unterbau verwendet sie entweder die C-Standardfunktionen setjmp und longjmp oder die von System V stammenden Funktionen makecontext, setcontext, getcontext und swapcontext. Letztere sind 2008 wegen einer läppischen Formalität und mangelnden Gebrauchs ersatzlos aus dem Posix-Standard geflogen, aber auf vielen Systemen trotzdem vorhanden.

C++-Nutzer können zum Beispiel auf die Bibliothek Boost.Coroutine [c] zurückgreifen, die Giovanni Deretta 2006 im Rahmen des Google Summer of Code entwickelte und die ungeachtet des Namens nicht zu den offiziellen Boost-Bibliotheken [d] gehört. Die bieten mit Boost.Context eine Bibliothek, die ähnlich wie makecontext und Co. den Wechsel zu einem anderen Ausführungskontext gestattet und als Grundlage für Koroutinen dienen kann [e]. Eine darauf aufbauende Reimplementierung von Boost.Coroutine ist zurzeit in der Entwicklung [f, g].

Scheme bietet mit seinen Continuations bereits alles, was man zum Bau von Koroutinen benötigt. Perl-Programmierer können das Modul Coro nutzen [h]. Obendrein besteht prinzipiell die Möglichkeit, Koroutinen auf System- oder Sprachebene mit Threads zu implementieren. Dabei können neben nativen auch User-Space-Threads zum Einsatz kommen, wie sie etwa die GNU-Bibliothek Pth bereitstellt [i]. Schöner wäre es allerdings, wenn verbreitete Sprachen wie C, C++ und Java von Haus aus Koroutinen bieten würden.

Alle Links: www.ix.de/ix1302158 (mr)