Go-Programme, die mehrere gleichzeitige Berechnungen in Goroutines ausführen, müssen ihre Lebensdauer verwalten. Runaway-Goroutinen können in unendliche Schleifen geraten, andere wartende Goroutinen blockieren oder einfach zu lange dauern. Im Idealfall sollten Sie in der Lage sein, Goroutinen zu stornieren oder nach einer Mode auszulaufen.
Geben Sie eine inhaltsbasierte Programmierung ein. Mit Go 1.7 wurde das Kontextpaket eingeführt, das genau diese Funktionen bietet sowie die Möglichkeit, beliebige Werte mit einem Kontext zu verknüpfen, der mit der Ausführung von Anforderungen verbunden ist und die Kommunikation außerhalb des Bandes und die Weitergabe von Informationen ermöglicht.
In diesem Lernprogramm erfahren Sie, wie und wo Sie Kontexte von Go kennen, wann und wie Sie sie verwenden und wie Sie deren Missbrauch vermeiden können.
Der Kontext ist eine sehr nützliche Abstraktion. Sie können damit Informationen einkapseln, die für die Kernberechnung nicht relevant sind, z. B. Anforderungs-ID, Berechtigungstoken und Zeitüberschreitung. Diese Kapselung bietet mehrere Vorteile:
Hier ist die gesamte Kontextoberfläche:
Typ Kontext-Schnittstelle Deadline () (Deadline-Zeit.Time, ok Bool) Done () <-chan struct Err() error Value(key interface) interface
In den folgenden Abschnitten wird der Zweck der einzelnen Methoden erläutert.
Deadline gibt die Zeit zurück, zu der die für diesen Kontext geleistete Arbeit abgebrochen werden soll. Deadline kehrt zurück ok == falsch
wenn keine Frist gesetzt ist. Aufeinanderfolgende Anrufe zu Deadline liefern die gleichen Ergebnisse.
Done () gibt einen Kanal zurück, der geschlossen wird, wenn die Arbeit für diesen Kontext abgebrochen werden soll. Done darf Null zurückgeben, wenn dieser Kontext niemals aufgehoben werden kann. Aufeinanderfolgende Aufrufe von Done () geben denselben Wert zurück.
Done kann in select-Anweisungen verwendet werden:
// Stream generiert Werte mit DoSomething und sendet sie // an out, bis DoSomething einen Fehler zurückgibt oder ctx.Done // geschlossen ist. func Stream (ctx context.Context, out chan.)<- Value) error for v, err := DoSomething(ctx) if err != nil return err select case <-ctx.Done(): return ctx.Err() case out <- v:
In diesem Artikel des Go-Blogs finden Sie weitere Beispiele zur Verwendung eines Fertigkanals für die Stornierung.
Err () gibt Null zurück, solange der Done-Kanal geöffnet ist. Es kehrt zurück Abgebrochen
wenn der Kontext abgebrochen wurde oder DeadlineExceeded
wenn die Frist des Kontexts abgelaufen ist oder das Timeout abgelaufen ist. Nachdem Done geschlossen wurde, geben aufeinanderfolgende Aufrufe von Err () denselben Wert zurück. Hier sind die Definitionen:
// Abgebrochen ist der Fehler, der von Context.Err zurückgegeben wird, wenn der // Kontext abgebrochen wird. var Canceled = Fehler. Neu ("Kontext abgebrochen"). // DeadlineExceeded ist der Fehler, der von Context.Err // zurückgegeben wird, wenn die Deadline des Kontextes überschritten wird. var DeadlineExceeded Fehler = DeadlineExceededError
Value gibt den mit diesem Kontext verknüpften Wert für einen Schlüssel zurück, oder nil, wenn dem Schlüssel kein Wert zugeordnet ist. Aufeinanderfolgende Aufrufe von Value mit demselben Schlüssel führen zu demselben Ergebnis.
Verwenden Sie Kontextwerte nur für Daten mit Anforderungsbereich, die Prozesse und API-Grenzen überschreiten, nicht für die Übergabe optionaler Parameter an Funktionen.
Ein Schlüssel identifiziert einen bestimmten Wert in einem Kontext. Funktionen, die Werte in Context speichern möchten, weisen normalerweise einen Schlüssel in einer globalen Variablen zu und verwenden diesen Schlüssel als Argument für context.WithValue () und Context.Value (). Ein Schlüssel kann ein beliebiger Typ sein, der Gleichheit unterstützt.
Kontexte haben Geltungsbereiche. Sie können Bereiche von anderen Bereichen ableiten, und der übergeordnete Bereich hat keinen Zugriff auf Werte in abgeleiteten Bereichen, abgeleitete Bereiche haben jedoch Zugriff auf die Werte des übergeordneten Bereichs.
Die Kontexte bilden eine Hierarchie. Sie beginnen mit context.Background () oder context.TODO (). Wenn Sie WithCancel (), WithDeadline () oder WithTimeout () aufrufen, erstellen Sie einen abgeleiteten Kontext und erhalten eine Abbruchfunktion. Wichtig ist, dass alle abgeleiteten Kontexte eines übergeordneten Kontexts abgebrochen oder abgelaufen sind.
Sie sollten context.Background () in der main () - Funktion, in init () - Funktionen und in Tests verwenden. Sie sollten context.TODO () verwenden, wenn Sie nicht sicher sind, welchen Kontext Sie verwenden möchten.
Beachten Sie, dass Hintergrund und TODO sind nicht stornierbar.
Wie Sie sich erinnern, geben WithDeadline () und WithTimeout () Kontexte zurück, die automatisch storniert werden, während WithCancel () einen Kontext zurückgibt und explizit abgebrochen werden muss. Alle geben eine Abbruchfunktion zurück. Auch wenn das Timeout / die Deadline noch nicht abgelaufen ist, können Sie den abgeleiteten Kontext trotzdem abbrechen.
Schauen wir uns ein Beispiel an. Zuerst ist hier die Funktion contextDemo () mit einem Namen und einem Kontext. Es wird in einer Endlosschleife ausgeführt, wobei der Name und gegebenenfalls der Abgabetermin der Konsole an die Konsole ausgegeben werden. Dann schläft es nur für eine Sekunde.
Pakethauptimport ("fmt" "context" "time") func contextDemo (Namenszeichenfolge, ctx context.Context) für if ok fmt.Println (name, "verfällt um:", Deadline) else fmt .Println (Name, "hat keine Frist") time.Sleep (time.Second)
Die Hauptfunktion erzeugt drei Kontexte:
Anschließend wird die contextDemo-Funktion als drei Goroutinen gestartet. Alle laufen gleichzeitig und drucken jede Sekunde ihre Nachricht.
Die Hauptfunktion wartet dann darauf, dass die Goroutine mit timeoutCancel abgebrochen wird, indem sie aus ihrem Done () - Kanal liest (wird blockiert, bis sie geschlossen wird). Wenn das Zeitlimit nach drei Sekunden abgelaufen ist, ruft main () das cancelFunc () auf, das die Goroutine mit cancelContext sowie die letzte Goroutine mit dem abgeleiteten 4-Stunden-Terminkontext abbricht.
func main () timeout: = 3 * time.econd Deadline: = time.Now (). Add (4 * time.Hour) timeOutContext, _: = context.WithTimeout (context.Background (), timeout) cancelContext, cancelFunc : = context.WithCancel (context.Background ()) deadlineContext, _: = context.WithDeadline (cancelContext, deadline) go contextDemo ("[timeoutContext]"), timeOutContext) go contextDemo ("[cancelContext]"), cancelContext) go contextDemo ( "[deadlineContext]", deadlineContext) // Warten Sie, bis der Timeout abgelaufen ist <- timeOutContext.Done() // This will cancel the deadline context as well as its // child - the cancelContext fmt.Println("Cancelling the cancel context… ") cancelFunc() <- cancelContext.Done() fmt.Println("The cancel context has been cancelled… ") // Wait for both contexts to be cancelled <- deadlineContext.Done() fmt.Println("The deadline context has been cancelled… ")
Hier ist die Ausgabe:
[cancelContext] hat keine Deadline [deadlineContext] läuft ab am: 2017-07-29 09: 06: 02.34260363 [timeoutContext] läuft ab am: 2017-07-29 05: 06: 05.342603759 [cancelContext] hat keine Deadline [timeoutContext] verfallen am: 2017-07-29 05: 06: 05.342603759 [deadlineContext] läuft ab am: 2017-07-29 09: 06: 02.34260363 [cancelContext] hat keine Frist [timeoutContext] läuft ab am: 2017-07-29 05: 06: 05.342603759 [deadlineContext] läuft aus um: 2017-07-29 09: 06: 02.34260363 Abbrechen des Abbruchkontexts… Der Abbruchkontext wurde abgebrochen… Der Abrufkontext wurde abgebrochen…
Mit der WithValue () - Funktion können Sie einem Kontext Werte hinzufügen. Beachten Sie, dass der ursprüngliche Kontext zurückgegeben wird, nicht ein abgeleiteter Kontext. Sie können die Werte mithilfe der Value () -Methode aus dem Kontext lesen. Lassen Sie uns unsere Demofunktion ändern, um ihren Namen aus dem Kontext zu erhalten, anstatt ihn als Parameter zu übergeben:
func contextDemo (ctx context.Context) Deadline, ok: = ctx.Deadline () name: = ctx.Value ("name") für if ok fmt.Println (name, "verfällt um:", deadline) else fmt.Println (Name, "hat keine Frist") time.Schlaf (time.Second)
Und lassen Sie uns die Hauptfunktion ändern, um den Namen über WithValue () anzuhängen:
go contextDemo (context.WithValue (timeOutContext, "name", "[timeoutContext]")) go contextDemo (context.WithValue (cancelContext, "name", "[cancelContext]")) gehe contextDemo (context.WithValue (deadlineContext, ") Name "," [DeadlineContext] "))
Die Ausgabe bleibt gleich. Im Abschnitt "Best Practices" finden Sie einige Richtlinien zur richtigen Verwendung von Kontextwerten.
Es wurden mehrere Best Practices für Kontextwerte entwickelt:
Einer der nützlichsten Anwendungsfälle für Kontexte ist das Übergeben von Informationen zusammen mit einer HTTP-Anforderung. Diese Informationen können eine Anforderungs-ID, Authentifizierungsnachweise und mehr enthalten. In Go 1.7 nutzte das Standardpaket net / http das Kontextpaket, das "standardisiert" wurde, und fügte die Kontextunterstützung direkt zum Anforderungsobjekt hinzu:
func (r * Request) Kontext () context.Context func (r * Request) WithContext (ctx context.Context) * Anforderung
Es ist jetzt möglich, eine Anforderungs-ID aus den Kopfzeilen bis zum endgültigen Handler auf standardmäßige Weise anzufügen. Die WithRequestID () - Handlerfunktion extrahiert eine Anforderungs-ID aus dem Header "X-Request-ID" und generiert einen neuen Kontext mit der Anforderungs-ID aus einem vorhandenen Kontext, den sie verwendet. Es gibt es dann an den nächsten Handler in der Kette weiter. Die öffentliche Funktion GetRequestID () bietet Zugriff auf Handler, die in anderen Paketen definiert sein können.
const requestIDKey int = 0 func WithRequestID (nächster http.Handler) http.Handler gibt http.HandlerFunc zurück (func (rw http.ResponseWriter, req * http.Request) // Extrahieren der Anforderungs-ID aus der Anforderungskopf-ReqID: = req.Header .Get ("X-Request-ID") // Neuen Kontext aus dem Anforderungskontext mit // der Anforderungs-ID erstellen ctx: = context.WithValue (req.Context (), requestIDKey, reqID) // Neue Anforderung mit der neuen erstellen context req = req.WithContext (ctx) // Lassen Sie den nächsten Handler in der Kette übernehmen. next.ServeHTTP (rw, req)) func GetRequestID (ctx context.Context) Zeichenfolge ctx.Value (requestIDKey). string) func Handle (rw http.ResponseWriter, req * http.Request) reqID: = GetRequestID (req.Context ())… func main () handler: = WithRequestID (http.HandlerFunc (Handle)) http. ListenAndServe ("/", Handler)
Die kontextbasierte Programmierung bietet eine standardisierte und gut unterstützte Methode, um zwei häufig auftretende Probleme zu lösen: die Verwaltung der Lebensdauer von Goroutines und die Weitergabe von Out-of-Band-Informationen über eine Funktionskette.
Folgen Sie den Best Practices und verwenden Sie Kontexte im richtigen Kontext (sehen Sie, was ich dort gemacht habe?), Und Ihr Code wird sich erheblich verbessern.