Aus Linux-Magazin 07/2021

Golang: Skript-Schreiber stöhnen, C-Programmierer jubeln (Seite 5)

Channels: Synchrones Sprachrohr

Verschiedene nebenläufige Programmteile in Go schicken sich oft Nachrichten über sogenannte Channels. Deren Funktion geht über das einer luftpostartigen Unix-Pipe hinaus, denn Sender und Empfänger synchronisieren sich damit nebenher miteinander auf elegante Weise, ohne dass umständlich zu handhabende Softwarestrukturen wie Semaphoren gebraucht würden.

Listing 7

block.go

package main
func main() {
  ch := make(chan bool)
  ch <- true // blocking
  <-ch
}

Liest ein Go-Programm aus einem Channel, in den niemand schreibt, blockiert die lesende Goroutine den Programmfluss, bis im Channel etwas ankommt. Versucht umgekehrt eine Goroutine aus einem Channel zu lesen, in den niemand schreibt, blockiert sie ebenso, bis eine Nachricht vorliegt.

Wer demnach versucht, in einem Go-Programm erst aus einem Channel zu lesen und dann etwas hineinzuschreiben, oder umgekehrt erst zu schreiben und dann zu lesen, schreibt das langweiligste Go-Programm der Welt: Es blockiert permanent (Listing 7). Läuft sonst auch nichts, stellt die Go-Runtime einen Deadlock fest und bricht das Programm mit einem Fehler ab:

fatal error: all goroutines are asleep - deadlock!

Lese- und Schreibanweisungen an einen Channel müssen also immer in verschiedenen nebenläufigen Goroutinen stehen. Listing 8 erzeugt zum Beispiel zwei Channels »ping« und »ok«, die Nachrichten vom Typ »bool« aufnehmen (»true« oder »false«). Dann setzt das Hauptprogramm (das als Goroutine unterwegs ist) eine weitere Goroutine in Gang. Die versucht, parallel aus dem Channel »ping« zu lesen, und blockiert damit.

Listing 8

chan.go

package main
import (
  "fmt"
)
func main() {
  ping := make(chan bool)
  ok := make(chan bool)
  go func() {
    select {
    case <-ping:
      fmt.Printf("Ok!\n")
      ok <- true
    }
  }()
  fmt.Printf("Ping!\n")
  ping <- true
  <-ok
}

Mittlerweile fährt das Hauptprogramm fort und schreibt einen booleschen Wert in den Channel »ping«. Sobald die parallele Goroutine am anderen Ende lauscht, geht der Schreibvorgang durch, und das Hauptprogramm hängt nun an der lesenden Anweisung des »ok«-Channels am Ende von Listing 8.

Die parallele Go-Routine, die mit der Variablen »ok« ebenfalls Zugriff auf den Channel hat, schreibt nun einen booleschen Wert in »ok« hinein, worauf die letzte Zeile des Hauptprogramms die Blockade aufgibt und das Programm endet – ein perfekter Handschlag, mit dem sich zwei Goroutinen, die des Hauptprogramms und die zusätzlich gestartete, miteinander verabreden, also synchronisieren.

Die Ausgabe des aus Listing 8 kompilierten Binaries lautet »”Ping!”« und »”Ok!”«, und zwar genau in dieser Reihenfolge und niemals außer der Reihe, denn das gezeigte Channel-Arrangement schließt gefürchtete Race Conditions kategorisch aus.

Nicht mehr als zwei

Normalerweise puffern Channels erhaltene Eingaben nicht, wie das Beispiel in Listing 7 gezeigt hat, das einfach nur den Programmfluss blockierte. Wer im Normalfall will, dass ein Leser lesen kann, ohne zu blockieren, muss dafür sorgen, dass ein Schreiber parallel in den Channel hineinschreibt. Andererseits können gepufferte Channels Daten vorhalten, also kann ein Schreiber hineinschreiben, ohne zu blockieren, auch wenn gerade niemand liest. Dockt irgendwann ein Leser an, holt der die Daten aus dem Puffer.

Gepufferte Channels bieten ein Werkzeug, um die maximale Anzahl gleichzeitig laufender Goroutinen zu limitieren. Das vermeidet bei rechenintensiven Arbeiten, die CPU mit einer einzigen Applikation zu überlasten. Listing 9 feuert in einer For-Schleife zehn Goroutinen ab, doch ein gepufferter Channel lässt immer nur zwei gleichzeitig laufen. Wie funktioniert das?

Listing 9

limit.go

package main
import (
  "fmt"
  "time"
)
func main() {
  limit := make(chan bool, 2)
  for i := 0; i < 10; i++ {
    go func(i int) {
      limit <- true
      work(i)
      <-limit
    }(i)
  }
  time.Sleep(10 * time.Second)
}
func work(id int) {
  fmt.Printf("%d start\n", id)
  time.Sleep(time.Second)
  fmt.Printf("%d end\n", id)
}
DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 8 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Comments
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben