Channels in Go: Bequem parallel

Viele fürchten nebenläufige Programmierung. Channels in Go können diese Art der Softwareentwicklung aber wesentlich erleichtern.

In Pocket speichern vorlesen Druckansicht 7 Kommentare lesen
Go
Lesezeit: 16 Min.
Von
  • Dr. Reinhard Wobst
Inhaltsverzeichnis

Moderne Prozessoren bieten viele CPU-Kerne, aber die meisten Programme nutzen diese nicht aus. Entwickler haben dafür eine einleuchtende Erklärung: Sie fürchten kaum mehr als sogenannte Race Conditions. Hierbei handelt es sich um Fehler, die aus unterschiedlicher Reihenfolge der Ausführung parallel laufender Programme und Threads resultieren und überwiegend nicht reproduzierbar sind – von sicheren Tests ganz zu schweigen.

Das ist zum Teil auch der Implementierung der Nebenläufigkeit geschuldet. Entwickler greifen üblicherweise auf Mutexe zurück, auch Locks genannt: Dabei handelt es sich um Status-Flags, die das Programm Thread-sicher verändern und somit anderen Threads den Zugriff auf Ressourcen sperren können –, wenn die anderen die Sperre berücksichtigen.

Der verbreitete Einsatz von Mutexen geht unter anderem auf eine Arbeit von Birell vom System Research Center aus dem Jahr 1989 zurück. Damals waren Threads noch etwas ganz Neues, und bei Parallelisierung dachte er wohl eher noch an Vektorprozessoren in Supercomputern.

Dabei entwarf der britische Informatiker Tony Hoare schon elf Jahre früher mit den Communicating Sequential Processes (CSP) ein anderes Konzept. Der Gedanke ist einfach: CSPs regeln den Zugriff auf Daten nicht per Mutex, denn das erinnert unangenehm an das kooperative Multitasking. Stattdessen reicht diese Technik die Daten einfach herüber: Wer sie bekommt, darf damit arbeiten. Letztlich beruht die viel gepriesene Sicherheit von Rust auch auf dieser Art des Vorgehens. Leider werden konkurrierende Datenzugriffe aber immer noch zuerst mit Mutexen assoziiert – außer bei Sprachen wie Go und Rust, denn Go hat das Konzept der CSP von Anfang an eingebaut. Das ist vielleicht das nützlichste und oft in seiner Bedeutung übersehene Feature dieser Sprache, streng nach dem Motto: "Kommuniziere nicht, indem du Speicher teilst, sondern teile Speicher, indem du kommunizierst."

Dieses Kommunizieren geschieht über Channels, also typstrenge Datenleitungen, die die Integrität der Daten sichern. Wenn ein Thread ein Objekt in einen Channel schreibt, muss ein anderer Thread, der in den gleichen Channel schreiben will, warten (so wie die Queue in Python). Dadurch kommt ein Objekt immer genauso am anderen Ende an, wie es gesendet wurde – niemand darf "dazwischenreden". Channels bilden eine eigene Typklasse und der CSP erzeugt sie unabhängig von Sender oder Empfänger.

Wer sich schon einmal mit drei oder mehr voneinander abhängigen Mutexen herumschlagen musste, weiß, dass diese Logik extrem fehleranfällig ist. Bei Channels ist es einfacher: Man muss nicht darüber nachdenken, wer wann auf was zugreifen darf, sondern hat den Strom der Daten sozusagen vor der Nase.