Plugins in Go schreiben

Go konnte den Code vor Go 1.8 nicht dynamisch laden. Ich bin ein großer Befürworter von Plugin-basierten Systemen, für die Plugins in vielen Fällen dynamisch geladen werden müssen. Ich habe sogar darüber nachgedacht, ein Plugin-Paket zu schreiben, das auf C-Integration basiert.

Ich bin sehr aufgeregt, dass die Go-Designer diese Funktion der Sprache hinzugefügt haben. In diesem Lernprogramm erfahren Sie, warum Plugins so wichtig sind, welche Plattformen derzeit unterstützt werden und wie Plugins in Ihren Programmen erstellt, erstellt, geladen und verwendet werden.   

Die Gründe für Go-Plugins

Go-Plugins können für viele Zwecke verwendet werden. Sie lassen Sie Ihr System in eine generische Engine zerlegen, die leicht zu verstehen und zu testen ist, und viele Plugins unterliegen einer strengen Schnittstelle mit klar definierten Verantwortlichkeiten. Plugins können unabhängig vom Hauptprogramm entwickelt werden, das sie verwendet. 

Das Programm kann verschiedene Kombinationen von Plugins und sogar mehrere Versionen desselben Plugins gleichzeitig verwenden. Die scharfen Grenzen zwischen Hauptprogramm und Plugins fördern die bewährte Methode der lockeren Kopplung und Trennung von Anliegen.

Das "Plugin" -Paket

Das neue, in Go 1.8 eingeführte "Plugin" -Paket hat einen sehr engen Umfang und eine sehr enge Schnittstelle. Es bietet die Öffnen() Funktion zum Laden einer gemeinsam genutzten Bibliothek, die ein Plugin-Objekt zurückgibt. Das Plugin-Objekt hat eine Sieh nach oben() Eine Funktion, die ein Symbol (leere Schnittstelle ) zurückgibt, kann vom Typ für eine Funktion oder eine Variable angegeben werden, die vom Plugin verfügbar gemacht wird. Das ist es.

Plattformunterstützung

Das Plugin-Paket wird derzeit nur unter Linux unterstützt. Es gibt jedoch Möglichkeiten, mit Plugins auf jedem Betriebssystem zu spielen.

Vorbereiten einer Docker-basierten Umgebung

Wenn Sie an einer Linux-Box arbeiten, müssen Sie nur Go 1.8 installieren und schon kann es losgehen. Wenn Sie jedoch unter Windows oder Mac OS arbeiten, benötigen Sie eine Linux-VM oder einen Docker-Container. Um es zu verwenden, müssen Sie zunächst Docker installieren.

Wenn Sie Docker installiert haben, öffnen Sie ein Konsolenfenster und geben Sie Folgendes ein: docker run -it -v ~ / go: / go golang: 1.8-wheezy bash

Dieser Befehl bildet mein lokales ab $ GOPATH beim / / gehen zu /gehen im Behälter. Dadurch kann ich den Code mit meinen bevorzugten Tools auf dem Host bearbeiten und im Container für das Erstellen und Ausführen in der Linux-Umgebung verfügbar machen.

Weitere Informationen zu Docker finden Sie auf Envato Tuts + in meiner Serie "Docker From the Ground Up":

  • Docker von Grund auf: Bilder verstehen
  • Docker Von Grund auf: Bilder bauen
  • Docker Von Grund auf: Arbeiten mit Containern, Teil 1
  • Docker Von Grund auf: Arbeiten mit Containern, Teil 2

Go-Plugin erstellen

Ein Go-Plugin sieht aus wie ein reguläres Paket, und Sie können es auch als normales Paket verwenden. Es wird nur dann zu einem Plugin, wenn Sie es als Plugin erstellen. Hier sind ein paar Plugins, die ein implementieren Sortieren()Funktion, die ein Segment von ganzen Zahlen sortiert. 

QuickSort Plugin

Das erste Plugin implementiert einen naiven QuickSort-Algorithmus. Die Implementierung funktioniert in Slices mit eindeutigen Elementen oder mit Duplikaten. Der Rückgabewert ist ein Zeiger auf ein Segment von Ganzzahlen. Dies ist nützlich für Sortierfunktionen, die ihre Elemente sortieren, da sie ohne Kopieren zurückgegeben werden können. 

In diesem Fall erstelle ich tatsächlich mehrere Zwischenschnitte, sodass der Effekt meistens verschwendet wird. Aus Gründen der Lesbarkeit opfere ich hier auf Leistung, da das Ziel darin besteht, Plugins zu demonstrieren und keinen extrem effizienten Algorithmus zu implementieren. Die Logik lautet wie folgt:

  • Wenn es keine oder nur einen Artikel gibt, geben Sie den ursprünglichen Abschnitt zurück (bereits sortiert)..
  • Wählen Sie ein zufälliges Element als Stift.
  • Fügen Sie alle Elemente hinzu, die kleiner als der Stift sind unten Scheibe.
  • Fügen Sie alle Elemente hinzu, die größer als der Stift sind über Scheibe. 
  • Fügen Sie alle Elemente hinzu, die dem Peg gleich sind Mitte Scheibe.

An diesem Punkt ist die Mitte slice wird sortiert, da alle Elemente gleich sind (wenn der Stift doppelt vorhanden ist, werden hier mehrere Elemente angezeigt). Nun kommt der rekursive Teil. Es sortiert die unten und über Scheiben durch Aufrufen Sortieren() nochmal. Wenn diese Anrufe zurückkehren, werden alle Slices sortiert. Wenn Sie sie einfach anhängen, erhalten Sie eine vollständige Sortierung der ursprünglichen Artikel.

Paket Hauptimport "math / rand" func Sort (items [] int) * [] int if len (items) < 2  return &items  peg := items[rand.Intn(len(items))] below := make([]int, 0, len(items)) above := make([]int, 0, len(items)) middle := make([]int, 0, len(items)) for _, item := range items  switch  case item < peg: below = append(below, item) case item == peg: middle = append(middle, item) case item > peg: oben = Anhängen (oben, Element) unten = * Sortieren (unten) oben = * Sortieren (oben) sortiert: = Anhängen (unten, Mitte ...), oben ...) zurückkehren & sortieren

BubbleSort Plugin

Das zweite Plugin implementiert den BubbleSort-Algorithmus auf naive Weise. BubbleSort wird oft als langsam betrachtet, aber für eine kleine Anzahl von Elementen und mit ein paar geringfügigen Optimierungsmaßnahmen schlägt es häufig kompliziertere Algorithmen wie QuickSort. 

Es ist üblich, einen hybriden Sortieralgorithmus zu verwenden, der mit QuickSort beginnt. Wenn die Rekursion zu klein genug ist, wechselt der Algorithmus zu BubbleSort. Das Bubble-Sort-Plugin implementiert a Sortieren() Funktion mit derselben Signatur wie der Schnellsortieralgorithmus. Die Logik lautet wie folgt:

  • Wenn es keine oder nur einen Artikel gibt, geben Sie den ursprünglichen Abschnitt zurück (bereits sortiert)..
  • Über alle Elemente iterieren.
  • Durchlaufen Sie in jeder Iteration die restlichen Elemente.
  • Tauschen Sie das aktuelle Element gegen ein beliebiges Element aus, das größer ist.
  • Am Ende jeder Iteration befindet sich das aktuelle Element an der richtigen Stelle.
Paket main func Sort (items [] int) * [] int if len (items) < 2  return &items  tmp := 0 for i := 0; i < len(items); i++  for j := 0; j < len(items)-1; j++  if items[j] > items [j + 1] tmp = items [j] items [j] = items [j + 1] items [j + 1] = tmp return & items 

Plugin erstellen

Jetzt haben wir zwei Plugins, die wir erstellen müssen, um eine gemeinsam nutzbare Bibliothek zu erstellen, die von unserem Hauptprogramm dynamisch geladen werden kann. Der Befehl zum Bauen lautet: go build -buildmode = plugin

Da wir über mehrere Plugins verfügen, habe ich jedes in einem separaten Verzeichnis unter einem freigegebenen "Plugins" -Verzeichnis abgelegt. Hier ist das Verzeichnislayout des Plugins-Verzeichnisses. In jedem Plugin-Unterverzeichnis befindet sich die Quelldatei "_plugin.go "und ein kleines Shellskript" build.sh "zum Erstellen des Plugins. Die endgültigen .so-Dateien werden in das übergeordnete" plugins "-Verzeichnis verschoben:

$ tree plugins plugins ├── bubble_sort ├── ├── bubble_sort_plugin.go └──. build.sh ├── bubble_sort_plugin.so ├── quick_sort build.sh └── _ quick_sort_plugin.go └── quick_sort_plugin .so

Die * .so-Dateien werden im Plugins-Verzeichnis gespeichert, weil sie vom Hauptprogramm leicht erkannt werden können, wie Sie später sehen werden. Der tatsächliche Build-Befehl in jedem "build.sh" -Skript gibt an, dass die Ausgabedatei in das übergeordnete Verzeichnis gespeichert werden soll. Für das Bubble-Sort-Plugin lautet es beispielsweise:

go build -buildmode = plugin -o… /bubble_sort_plugin.so

Laden des Plugins

Um das Plugin laden zu können, müssen Sie wissen, wo sich die Ziel-Plugins (die gemeinsam genutzten Bibliotheken * .so befinden). Dies kann auf verschiedene Arten erfolgen:

  • Befehlszeilenargumente übergeben
  • eine Umgebungsvariable setzen
  • mit einem bekannten Verzeichnis
  • eine Konfigurationsdatei verwenden

Ein weiteres Problem ist, ob das Hauptprogramm die Plugin-Namen kennt oder ob es alle Plugins in einem bestimmten Verzeichnis dynamisch erkennt. Im folgenden Beispiel erwartet das Programm, dass sich unter dem aktuellen Arbeitsverzeichnis ein Unterverzeichnis mit dem Namen "plugins" befindet, und es lädt alle gefundenen plugins.

Der Anruf an die filepath.Glob ("plugins / *. so") Funktion gibt alle Dateien mit der Erweiterung ".so" im Unterverzeichnis plugins und zurück plugin.Open (Dateiname) lädt das Plugin. Wenn etwas schief geht, gerät das Programm in Panik.

Paket main import ("fmt" "plugin" "path / filepath") func main () all_plugins, err: = filepath.Glob ("plugins / *. so"), wenn err! = nil panic (err) für _, Dateiname: = Bereich (all_plugins) fmt.Println (Dateiname) p, err: = plugin.Open (Dateiname) wenn err! = nil panic (err) 

Verwendung des Plugins in einem Programm

Das Auffinden und Laden des Plugins ist nur die halbe Miete. Das Plugin-Objekt liefert die Sieh nach oben() Methode, die einen Symbolnamen angibt, gibt eine Schnittstelle zurück. Sie müssen diese Schnittstelle in ein konkretes Objekt (z. B. eine Funktion wie) eingeben Sortieren()). Es gibt keine Möglichkeit herauszufinden, welche Symbole verfügbar sind. Sie müssen nur deren Namen und ihren Typ kennen, damit Sie assert richtig eingeben können. 

Wenn das Symbol eine Funktion ist, können Sie es nach einer erfolgreichen Typübergabe wie jede andere Funktion aufrufen. Das folgende Beispielprogramm veranschaulicht alle diese Konzepte. Es lädt alle verfügbaren Plugins dynamisch, ohne zu wissen, welche Plugins vorhanden sind, außer dass sie sich im Unterverzeichnis "plugins" befinden. Es folgt, indem Sie in jedem Plugin das "Sort" -Symbol nachschlagen und dieses in eine Funktion mit der Signatur eingeben func ([] int) * [] int. Dann ruft es für jedes Plugin die Sortierfunktion mit einem Integer-Segment auf und gibt das Ergebnis aus.

Hauptimport des Pakets ("fmt" "plugin" "Pfad / Dateipfad") func main () numbers: = [] int 5, 2, 7, 6, 1, 3, 4, 8 // Die Plugins (die * .so-Dateien) müssen sich in einem 'plugins'-Unterverzeichnis all_plugins befinden, err: = filepath.Glob ("plugins / *. so"), wenn err! = nil panic (err) für _, Dateiname: = Bereich (all_plugins) p, err: = plugin.Open (Dateiname) wenn err! = nil panic (err) - Symbol, err: = p.Lookup ("Sort") wenn err! = nil panic (err) sortFunc, ok = Symbol. (func ([] int) * [] int) if! ok panic ("Plugin hat keine 'Sort ([] int) [] int' Funktion") Sortiert: = sortFunc (Zahlen) fmt.Println (Dateiname, sortiert) Ausgabe: plugins / bubble_sort_plugin.so & [1 2 3 4 5 6 7 8] plugins / quick_sort_plugin.so & [1 2 3 4 5 6 7 8] 

Fazit

Das "Plugin" -Paket bietet eine hervorragende Grundlage für das Schreiben anspruchsvoller Go-Programme, die Plugins dynamisch laden können, wenn dies erforderlich ist. Die Programmierschnittstelle ist sehr einfach und erfordert detaillierte Kenntnisse des zu verwendenden Programms auf der Plugin-Schnittstelle. 

Es ist möglich, ein erweitertes und benutzerfreundlicheres Plugin-Framework über das "Plugin" -Paket zu erstellen. Hoffentlich wird es bald auf alle Plattformen portiert. Wenn Sie Ihre Systeme unter Linux bereitstellen, sollten Sie Plugins verwenden, um Ihre Programme flexibler und erweiterbarer zu machen.