Eine Einführung in Go

Auf der Suche nach einer leichtgewichtigen cross-kompilierbaren Programmiersprache bin ich auf Go gestoßen. Was dies für Vorteile hat werde ich später erläutern.

Ich arbeite lange und viel mit C#, XAML, PowerShell und trotzdem hat es mir diese low-level Sprache angetan.

Ich nehme euch nun mit auf die Reise und vielleicht wird der eine oder andere sich danach auch mal Go anschauen.

Ich werde hier nicht versuchen dir die Sprache didaktisch in seiner Komplettheit zu erklären. Vielmehr möchte ich Interesse wecken!

Kurze Einführung

Die Sprache wurde 2009 von Google vorgestellt, siehe auch diese Präsentation von Rob Pike. Also ein recht junge Programmiersprache.

Go, also known as Golang, is a statically typed, compiled programming language designed at Google by Robert Griesemer, Rob Pike, and Ken Thompson. Go is syntactically similar to C, but with memory safety, garbage collection, structural typing, and CSP-style concurrency.

Einführung aus der englischen Wikipedia, siehe hier.

Maskottchen

Das Maskottchen ist die Taschenratten (engl. Gopher).

Typische Darstellung
Oder auch sowas aus „The Gopher way

Hello World

Hier natürlich das obligatorische Hello World Programm:

package main

import "fmt"

func main() {
    fmt.Println("hello world")
}
Nach der Installation der SDK kann man recht einfach das Hello World Programm kompilieren und ausführen.

Wie hat Go meine Aufmerksamkeit gewonnen?

Es gibt zwei Eigenschaften der Sprache Go die für mich ausschlaggebend waren, dieser Sprache eine Chance zu geben. Einmal die Möglichkeit von Cross Compile und die damit verbundene minimale Systemvoraussetzung und die Nebenläufigkeit die seit Geburt von Go ein wichtiger Bestandteil ist.

Cross Compile

Diese 24 Zeilen Powershell kompilieren einen funktionierenden WebServer für die Plattformen Windows, Linux, Linux auf ARM und macOS. D.h. ich kann auf meinem Windows-Rechner für meinen Raspberry Pi entwickeln und meine einzige Voraussetzung bei Linux/ARMv5 ist ein Kernel größer gleich 3.1.

Mehr zu den Systemvoraussetzung hier: https://github.com/golang/go/wiki/MinimumRequirements.

Nebenläufigkeit

Diese Beispiel zeigt wir man mit 5.000 nebenläufigen Berechnungen Pi ausrechnet. Die Kommunkation zwischen „goroutine“, dem Konstrukt zur Nebenläufigkeit, verläuft ausschließlich über Channels, also nicht wie bei Java, C++, C# über shared memory.

Do not communicate by sharing memory; instead, share memory by communicating.

https://blog.golang.org/share-memory-by-communicating

In Go wird für eine goroutine 2KB reserviert, wobei für einen Thread bei win32 1MB benötigt.

Der Vergleich hinkt ein wenig, da goroutines keine Threads sind. Da diese nur in virtuellen Speicher der Runtime existieren. Unter C# entspricht ein Thread auch einem Thread im Betriebssystem. Unter Go werden goroutines auf Threads verteilt und verwaltet.

Achtung! Nebenläufigkeit ist nicht Parallelisierung, siehe dazu auch Rob Pike – ‘Concurrency Is Not Parallelism’.

Was macht Go aus?

Professor der Informatik auf der KTH

Ich fand die Ausführung von Stefan Nilsson, Professor auf der KTH Royal Institute of Technology in Stockholm, Schweden zum Thema sehr zutreffend.

Er gliedert seine Ausführung „Why Go? – Key advantages you may have overlooked“ wie folgt.

  1. Minimalismus
  2. Code Transparenz
  3. Kompatibilität
  4. Performance

Sehr kurz wiedergegeben schreibt er:

Minimalismus: Go ist eine minimalistische Sprache. Die Spezifikation der Programmiersprache Go umfasst 50 Seiten (Java dagegen 750 Seiten) inklusive zahlreicher Beispiele. Ein erfahrener Entwickler kann nur anhand dieser Spezifikation einige über die Sprache lernen.

Code Transparenz: Um eine einfache Verständlichkeit des Codes zu erreichen gibt eine vorgegebene Formatierung, welche mit dem ausgelieferten Tool fmt angewendet werden kann. Nicht verwendete Pakete oder Variablen werden zur Kompilationszeit einen Fehler aufwerfen.

Kompatibilität: Es ist garantiert, dass die Major-Version von Go, aktuell 1, immer Rückwärtskompatibel bleibt. Dies hat sich auch in der Vergangenheit behauptet. Dazu ist das das gesamte Go-Projekt open source.

Performance: Go ist eine kompilierbare Sprache, dessen Kompilat aus einer einzigen portablen Datei besteht. Es gibt keine Abhängigkeiten zu VMs oder libs. Es gibt ein Cross-Compile zu den üblichen Betriebssystemen und Prozessorarchitekturen. Die zu erwartende Ausführungsgeschwindigkeit entspricht C++ oder Java. Ein Garbage Collector schützt gegen Memory Leaks, der GC hat eine sehr kleine Latenz, so dass dieser GC-Thread nicht auffallen sollte. Die Standard-Pakete sind von hoher Qualität und optimiert auf Geschwindigkeit, als positives Beispiel wird hier Regex erwähnt. Zum Schluss kann man noch sagen, die Kompilationszeit in Go ist gut und skaliert gut mit großen Projekten.

Engineering Manager bei Google

In Blogartikel „400 DAYS OF GO“ schreibt Philip O’Toole über die Produktivität von Go. Seine Aussagen versuche ich hier gekürzt wiederzugeben.

  1. Go braucht keine IDE, es reicht ein einfacher Texteditor.
  2. Die Kompilierungszeit ist sehr schnell, dass gesamte Projekt influxdb kann in 3 Sekunden erstellt werden. Entwickler die Erfahrung mit kompilieren und linken von C++ Code haben, kennen ganz andere Zeiten.
  3. Die Standard-Bibliotheken sind sehr umfangreich und von hoher Qualität, ergänzend kann man sagen ähnliche wie bei der .NET Runtime.
  4. Eingebaute Datenstrukturen wir Hash Tables und Dynamic Arrays
  5. Die Formatierung ist vorgegeben, mit der Go SDK wird das Tool go fmt zum formatieren ausgeliefert
  6. Es wird EINE statisch gelinkte ausführbare Datei kompiliert, alle Abhängigkeiten sind inkludiert. Unter Linux wird z.B. nur ein Kernel ab Version 2.6.23 (2007) benötigt
  7. Es gibt ein integriertes Test Framework, dadurch sind die Tests immer konsistent und der Entwickler muss sich nicht mit Third-Party Testframworks auseinandersetzen.
  8. Integrierte Performance Analyse Tools helfen beim Identifizieren von CPU und Memory Probleme.

Aber er scheibt auch über Dinge die nicht perfekt sind:

  1. Garbage Collection
  2. Boilerplate
  3. GOPATH

Noch ein bisschen Motivation…

Im Golang Repository auf GitHub ist eine interessante Sammlung von Erfolgsberichten zusammengestellt, siehe hier https://github.com/golang/go/wiki/SuccessStories. Diese Liste beinhaltet Beiträge u.a. von CloudFlare (1, 2), soundcloud.com (1), NTP Pool Project (1), bitly.com (1) und vielen mehr.

Im Golang Repository ist auch eine Aufführung von Entwicklern und Firmen die ausführlich erklären, wieso sie Go verwenden, siehe dazu hier https://github.com/golang/go/wiki/WhyGo.

Spannenden Projekte in Go geschrieben:

Wie kann ich Go lernen?

Es gibt einen sehr große Anzahl von Möglichkeiten um mit Go zu starten. Für mich hat der Wechsel von Go by Example und der Spezifikation gut funktioniert. Gefolgt vom Einlesen in Verschiedenen open source Projekten.

Es gibt unzählige Möglichkeiten einen Einstieg zu finden, hier ist z.B. eine Liste aus dem Go GitHub Projekt https://github.com/golang/go/wiki/Learn.

Hello World erklären

Schauen wir uns nochmal das Hello World Programm an:

package main // 1

import "fmt" // 2

func main() { // 3
    fmt.Println("hello world") // 4
}

Diese minimale Programm lässt sich wie folgt aufschlüsseln:

  1. Jede Go-Datei muss mit package name beginnen.
  2. Mit import werden Pakete eingebunden.
  3. Die Funktion func main() ist reserviert als Einstiegspunkt.
  4. Mit fmt. greifen wir auf das importierte Paket zu und rufen fmt.Println mit Funktion Println auf.
    Jedes Paket aus der Standard-Bibliothek ist dokumentiert, zu fmt.Println siehe hier: https://golang.org/pkg/fmt/#Println
    Als Parameter wird die Zeichenketten “Hello World” übergeben.
    Die geschweiften Klammern legen den Start und das Ende der Funktion main fest

Technische Eindrücke

Tooling

Für die ersten Go-Anwendung ist nur eine Sache notwendig, das GO SDK, siehe hier https://golang.org/dl/.

Eine IDE ist trotzdem zum empfehlen, hier kann z.B. VS Code oder das kostenpflichtige JetBrains GoLand verwendet werden.

Compile

Ein einfaches kompilieren wird einfach mit dem Argument build gestartet:
go build -o webservice.exe webservice.go

Die Parametrisierung erfolgt über Umgebungsvariablen oder Linker Flags.

Über Umgebungsvariablen kann z.B. das Zielsystem und Prozessorarchitektur festgelegt werden:

$env:GOOS = ‚windows‘; $env:GOARCH = ‚amd64‘; // Windows x64
$env:GOOS = ‚linux‘; $env:GOARCH = ‚amd64‘; // Linux x64
$env:GOOS = ‚linux‘; $env:GOARCH = ‚arm64‘; // Arm x64
$env:GOOS = ‚darwin‘; $env:GOARCH = ‚amd64‘; // macOS x64

Mit Linker Flags können z.B. die Debug-Symbols entfernt werden go build -ldflags "-s -w " , eine Variable im Code gesetzt werden -ldflags="-X 'main.Version=v1.0.0'" oder der Konsolenhost deaktiviert werden -ldflags "-H windowsgui".

Cross Compile

Ein Cross Compile gehört zu den sehr nützlichen Eigenschaften von Go.

Hier kann die Anwendung unter webservice.go einfach für 4 Plattformen gebaut werden:

$env:GOOS = 'windows'; $env:GOARCH = 'amd64'; go build -o webservice_demo.exe webservice.go
$env:GOOS = 'linux';   $env:GOARCH = 'amd64'; go build -o webservice_demo_linux webservice.go
$env:GOOS = 'linux';   $env:GOARCH = 'arm64'; go build -o webservice_demo_arm webservice.go
$env:GOOS = 'darwin';  $env:GOARCH = 'amd64'; go build -o webservice_demo_mac webservice.go

Die komplette Liste der GOOS und GOARCH hier hier zu finden: https://gist.github.com/asukakenji/f15ba7e588ac42795f421b48b8aede63

Cross Platform Compile
Cross Platform Compile ohne Debug-Symbole

Optimierung der Dateigröße

Ein Go-Kompilat ist leider nicht sehr klein, die oben gezeigt HelloWorld-Anwendung ist ganze 1.056 KB groß.

Wieso so große ausführbare Dateien? In dieser Datei ist nicht nur die Anweisung fmt.Println("hello world") enthalten, sondern auch die Runtime mit u.a. dem garbage collector, concurrency, stack management. Das Ziel an die Ausführung ist laut den Go Entwicklern „latency, ease of deployment, precise garbage collection, fast startup time, performance.“ Mit Optimierung in Go 1.7 wurde die Dateigröße auf bis zu 20% reduziert.

Diese Dateigröße lässt sich jedoch noch weiter reduzieren. Mit den Linker Flags entfernen wir die Debug Symbole und kommen damit auf 750 KB. Nun können wir via UPX das Kompilat sogar noch komprimieren wenn es das Einsatzszenario zulässt, so kommen wir dann sogar auf 242 KB.

Durch die Verwendung von UPX wird die Startup Time verlängert und eventuell die Speicherauslastung erhöht. Bei einem extrem kleinen System muss dies berücksichtigt werden.

Eigene Anwendungsbereiche

  • Sync nach Azure auf der Homematic Smart Home Zentrale CCU3 (ARM64)
  • Synchronisation von GitLab-Files mit dem Dateisystem (GitLabFileDownloader)
  • Analysetool für Systemintegratoren
  • Verwendung einer Post-quantum Cryptography Bibliothek
  • Kapseln von .NET Core Anwendungen
  • Temperatursensor mit Upload nach Azure (ARM64 auf einem Raspberry PI Nano)

Weiterführende Informationen