Koroutinen: Weniger warten, asynchron arbeiten

Koroutinen im Detail

Koroutinen zeichnen sich durch folgende Eigenschaften aus:

  • Sie sind unterbrechbar an "suspension points".
  • Sie nehmen ihre unterbrochene Berechnung wieder an der exakt gleichen Stelle mit dem gleichem Zustand auf.
  • Sie kommunizieren am besten über Kanäle.
  • Sie setzen die Kooperation mit dem Scheduler voraus. Sie werden nicht wie Threads einfach von außen unterbrochen, sondern müssen die Kontrolle freiwillig abgeben (kooperatives Multitasking).

Koroutinen sind unterdessen keine neue Erfindung, Melvin E. Conway erwähnte sie bereits 1963 zum ersten Mal in seinem Artikel Design of a Separable Transition-Diagram Compiler. Später erfolgte ihre Implementierung in Simula (1967) und Erlang (1986) sowie schließlich 2009 in Go. Bis dahin hatten es Koroutinen nie richtig geschafft, sich im Mainstream der Programmierfunktionen zu etablieren. In einführenden Lehrbüchern zur Nebenläufigkeit (siehe [2]) sind sie nicht einmal erwähnt.

Go und die Goroutinen

Der Verdienst für das Revival der Koroutinen ist sicherlich der Programmiersprache Go und deren Goroutinen zuzuschreiben. Im Rahmen der Google I/O 2012 präsentierte Rob Pike, einer der Go-Entwickler, die in der Programmiersprache unter der Bezeichnung Goroutinen implementierten Concurrency Patterns.

Der folgende Go-Code setzt das obige Kotlin-Beispiel mit einem Produzenten und zwei Konsumenten um:

package main

import (
"fmt"
"strconv"
"time"
)

func produce(c chan int) {
i := 1
for j := 0; j < 5; j++ {
fmt.Println("G " + strconv.Itoa(i))
c <- i
i *= 2
}
}

func consume(id string, c chan int) {
for i := 0; i < 2; i++ {
v := <-c
fmt.Println("C" + id + " " + strconv.Itoa(v))
time.Sleep(100 * time.Millisecond)
}
}

func main() {
channel := make(chan int)
go produce(channel)
go consume("A", channel)
go consume("B", channel)
time.Sleep(time.Second)
}

Eine Koroutine lässt sich in Go mit dem Statement go starten. Innerhalb der main-Funktion (die ebenfalls eine Goroutine ist) starten demnach drei Koroutinen. Das Sleep-Kommando am Ende ist notwendig, damit die Ausgaben angezeigt werden. Aus einem Kanal chan kann mit <-c gelesen und mit c <- geschrieben werden.

Wie das Beispiel zeigt, sind Koroutinen in Go und Kotlin auf den ersten Blick sehr ähnlich. Das erleichtert den Einstieg, wenn man bereits eine der Varianten kennt. Allerdings liegt der Teufel im Detail, denn unter der Haube gibt es doch größere Unterschiede.

Weitere Sprachen

Koroutinen stehen auch in vielen anderen Sprachen wie Python, C#, Ruby, Lua, Erlang und F# zur Verfügung. Die einzelnen Implementierungen unterscheiden sich zum Teil jedoch deutlich voneinander. So gibt es beispielsweise verschiedene Ansätze, die entweder auf eigene sogenannte Green-Threads aufbauen oder aber Threads nutzen, die das Betriebssystem zur Verfügung stellt. Eine detaillierte Diskussion der Koroutinen-Implementierungen würde den Rahmen des Artikels sprengen. Eine Übersicht der Implementierung findet sich beispielsweise bei Wikipedia.

Das LLVM-Projekt bietet Werkzeuge und Bibliotheken zur Erstellung von Compilern an, darunter Clang. Seit Version 5 unterstützt dieser Compiler Koroutinen experimentell. Dieser Vorstoß lässt sich durchaus als Vorbereitung darauf interpretieren, dass Koroutinen ab dem C++20-Standard offizielle Unterstützung erfahren.