Detaillierte Erläuterung der Rolle und Verwendung von Kontext in der Go-Sprache

KDP (Data Service Platform) ist ein von KaiwuDB unabhängig entwickeltes Datendienstprodukt. Mit KaiwuDB als Kern ist es eine One-Stop-Datendienstplattform für AIoT-Szenarien, die das industrielle Internet der Dinge, digitale Energie, das Internet der Fahrzeuge und intelligente Industrien erfüllt und anderen Branchen. Umfassende Geschäftsanforderungen für die Datenerfassung, -verarbeitung, -berechnung, -analyse und -anwendung in Kerngeschäftsszenarien, die Erkenntnis „Geschäft ist Daten, Daten sind Service“ und die Unterstützung von Unternehmen, einen größeren Geschäftswert aus Daten zu schöpfen.

Bei der Entwicklung der Echtzeit-Computing-Komponente der Datendienstplattform kann ein solches Problem auftreten: Die Echtzeit-Computing-Komponente bietet Benutzern die Funktion benutzerdefinierter Regeln, nachdem der Benutzer mehrere Regeln registriert und sie für einen bestimmten Zeitraum ausgeführt hat und dann die Definition der Regeln ändert und neu startet, tritt das Problem auf. Es ist ein Coroutine-Leck aufgetreten.

1. Echter Fall

In diesem Artikel wird Pseudocode verwendet, um das Problem des Coroutine-Lecks, das bei der Entwicklung von Echtzeit-Computing-Komponenten der Datendienstplattform auftreten kann, umfassend vorzustellen.

//规则的大致数据结构
type DummyRule struct{
    BaseRule    
    sorce []Source    
    sink  []Sink    
    //flow map key:flow 名称,value:flow 实例    
    flow map[string]Flow    
    ...
}

Die obige DummyRule ist die Regeldatenstruktur dieses Beispiels, die mehrere Datenquellenquellen, mehrere Datenzielsenken und einen Datenflussfluss umfasst. Der spezifische Prozess der Regeln ist wie folgt:

1 und 2 sind zwei Quellen. Verarbeiten Sie zunächst die beiden Quellen von 1 bzw. 2 durch Addition. Rufen Sie zweitens die Merge-Operation auf, um einen Stream zu synthetisieren. Führen Sie dann die Fanout-Operation aus, um zwei identische Streams zu generieren, die jeweils in 7 bzw. 8 fließen pass 7 und Der Zahlentyp 8 wird in eine Zeichenfolge umgewandelt und in die Dateien out1.txt bzw. out2.txt geschrieben.

type Source struct{
  consumers       []file.reader  
  out             chan interface{}  
  ctx             context.Context  
  cancel          context.CancelFunc  
  ...
}

Die obige Abbildung ist der Pseudocode der Datenquelle der Source-Klasse, Verbraucher sind die Leser, die zum Lesen von Dateidaten verwendet werden, out ist der Kanal, der zum Weiterleiten an den nächsten Datenstrom verwendet wird, und ctx ist der Kontext von Go. Es handelt sich um eine separate Coroutine für Verbraucher zum Lesen von Dateidaten. Die gelesenen Daten werden ausgegeben und warten auf den Verbrauch des nächsten Datenstroms.

type Sink struct{
   producers  []file.writer   
   in         chan interface{}   
   ctx        context.Context   
   cancel context.CancelFunc   
   ...
}

Die obige Abbildung ist der Pseudocode des Datenobjekts der Sink-Klasse. Der Produzent ist der Autor, der zum Schreiben der Datei verwendet wird Coroutine.

func(fm FlatMap) Via(flow streams.Flow) streams.Flow{
    go fm.transmit(flow)
    return flow
}

Die obige Abbildung zeigt den Quellcode der Datenflussübertragung. Die Verwendung von FlatMap ist curFlow:= prevFlow.Via(nextFlow), sodass der vorherige Flow an den nächsten Flow übergeben werden kann. Sie können sehen, dass ein Datenflussübertragungsprozess in einer Coroutine ausgeführt wird.

Aus dem vorherigen Quellcode können wir ersehen, dass diese Beispielregel mindestens 10 Coroutinen enthält, tatsächlich sind es jedoch viel mehr als 10 Coroutinen. Es ist ersichtlich, dass die Verwaltung von Coroutinen in den Echtzeit-Computing-Komponenten der Datendienstplattform sehr kompliziert ist.

Nach wiederholten Tests und Untersuchungen mit Tools wie go pprof, top und go Traces haben wir festgestellt, dass das Coroutine-Leck durch die falsche Löschung des Sink-Kontexts in den Regeln verursacht wurde.

Kontext ist eine wichtige Sprachfunktion für die Verwaltung von Goroutinen. Wenn Sie lernen, den Kontext richtig zu verwenden, können Sie die Beziehung zwischen Goroutinen besser klären und verwalten. Aus den obigen Beispielen können wir die Bedeutung des Kontexts erkennen. Durch das Erlernen der korrekten Verwendung von Kontext kann nicht nur die Codequalität verbessert werden, sondern auch viel Arbeit bei der Untersuchung von Coroutine-Lecks vermieden werden.

Zweitens, in den Kontext

1. Einleitung

Kontext wird normalerweise als Kontext bezeichnet. In der Go-Sprache wird darunter der laufende Zustand und die Szene der Goroutine verstanden. Es findet eine Übertragung des Kontexts zwischen oberen und unteren Goroutinen statt, und die oberen Goroutinen geben den Kontext an die unteren Goroutinen weiter.

Bevor jede Goroutine ausgeführt wird, muss sie den aktuellen Ausführungsstatus des Programms im Voraus kennen. Normalerweise werden diese Zustände in einer Kontextvariablen gekapselt und zur Ausführung an die Goroutine übergeben.

Wenn bei der Netzwerkprogrammierung eine Netzwerkanforderung empfangen und verarbeitet wird, kann sie in mehreren Goroutinen verarbeitet werden. Und diese Goroutinen müssen möglicherweise einige Informationen der Anfrage teilen. Wenn die Anfrage abgebrochen wird oder eine Zeitüberschreitung auftritt, werden auch alle aus dieser Anfrage erstellten Goroutinen beendet.

Das Go Context-Paket implementiert nicht nur die Methode zum Teilen von Statusvariablen zwischen Programmeinheiten, sondern kann auch Signale wie Ablauf oder Widerruf an das aufgerufene Programm übergeben, indem der Wert der ctx-Variablen auf einfache Weise außerhalb der aufgerufenen Programmeinheit festgelegt wird.

Wenn bei der Netzwerkprogrammierung A die API von B und B die API von C aufruft, dann sollte, wenn A B zum Abbrechen aufruft, auch der Aufruf von B an C abgebrochen werden. Das Context-Paket macht es sehr praktisch, Anforderungsdaten, Abbruchsignale und Timeout-Informationen zwischen Anforderungs-Goroutinen zu übergeben.

Der Kern des Context-Pakets ist die Context-Schnittstelle:

// A Context carries a deadline, a cancellation signal, and other values across
// API boundaries
//
// Context's methods may be called by multiple goroutines simultaneously.
type Context interface{     
     // 返回一个超时时间,到期则取消context。在代码中,可以通过deadline为io操作设置超过时间     
     Deadline() (deadline time.Time, ok bool)     
     // 返回一个channel,用于接收context的取消或者deadline信号。     
     // 当channel关闭,监听done信号的函数会立即放弃当前正在执行的操作并返回。     
     // 如果context实例时不可能取消的,那么     
     // 返回nil,比如空context,valueCtx     
     Done()
}

2. Anwendung

Für Goroutinen ist ihre Erstellungs- und Aufrufbeziehung immer wie ein Schicht-für-Schicht-Aufruf, wie eine Baumstruktur, und der Kontext oben sollte eine Möglichkeit haben, die Ausführung untergeordneter Goroutinen aktiv zu schließen. Um diese Beziehung zu realisieren, ist der Kontext ebenfalls eine Baumstruktur, und die Blattknoten werden immer vom Wurzelknoten abgeleitet.

Um einen Kontextbaum zu erstellen, sollte der erste Schritt darin bestehen, den Stammknoten abzurufen, und der Rückgabewert der Funktion Context.Backupgroup ist der Stammknoten.

func Background() Context{
    return background
}

Diese Funktion gibt einen leeren Kontext zurück, der im Allgemeinen von der ersten Goroutine erstellt wird, die die Anforderung empfängt, und der Wurzelknoten des Kontexts ist, der der eingehenden Anforderung entspricht. Er kann nicht abgebrochen werden, hat keinen Wert und keine Ablaufzeit. Er existiert oft als Kontext der obersten Ebene, der die Anfrage bearbeitet.

Mit dem Root-Knoten können Sie untergeordnete Knoten erstellen. Das Context-Paket bietet eine Reihe von Methoden zu deren Erstellung:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {}
func WithDeadline(parent Context, d time.Time)(Context, CancelFunc) {}
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {}
func WithValue(parent Context, key, val interface{}) Context {}

Die Funktion empfängt ein übergeordnetes Element vom Typ „Kontext“ und gibt einen Wert vom Typ „Kontext“ zurück, sodass Schicht für Schicht unterschiedliche Kontexte erstellt werden, der untergeordnete Knoten durch Kopieren des übergeordneten Knotens erhalten wird und einige Statuswerte des untergeordneten Knotens vorliegen Stellen Sie es entsprechend den empfangenen Parametern ein und dann kann der untergeordnete Knoten an die untere Goroutine übergeben werden.

Wie kann der geänderte Status über den Kontext übergeben werden?

In der übergeordneten Goroutine kann über die Withxx-Methode eine Abbruchmethode abgerufen werden, wodurch das Recht erhalten wird, den untergeordneten Kontext zu bedienen.

(1)MitAbbrechen

 Die WithCancel-Funktion besteht darin, den übergeordneten Knoten auf den untergeordneten Knoten zu kopieren und eine zusätzliche Variable vom Typ CancelFunc-Funktion zurückzugeben, die wie folgt definiert ist: Typ CancelFunc func()

Durch den Aufruf von CancelFunc wird das entsprechende untergeordnete Kontextobjekt abgebrochen. In der übergeordneten Goroutine kann der Kontext des untergeordneten Knotens über WithCancel erstellt und die Steuerung der untergeordneten Goroutine übernommen werden. Sobald die Funktion CancelFunc ausgeführt wird, ist der Kontext des untergeordneten Knotens beendet. Der untergeordnete Knoten benötigt zur Bestimmung den folgenden Code ob es vorbei ist und die Goroutine verlassen:

select {
case <- ctx.Cone():
    fmt.Println("do some clean work ...... ")
}

(2)Mit Frist

 Die Funktion von WithDeadline ähnelt der von WithCancel. Es kopiert auch den übergeordneten Knoten auf den untergeordneten Knoten, seine Ablaufzeit wird jedoch durch die Ablaufzeit von Frist und übergeordnetem Knoten bestimmt. Wenn die Ablaufzeit des übergeordneten Elements vor dem Stichtag liegt, ist die zurückgegebene Ablaufzeit dieselbe wie die Ablaufzeit des übergeordneten Elements. Wenn der übergeordnete Knoten abläuft, müssen alle untergeordneten Knoten gleichzeitig geschlossen werden.

(3)WithTimeout

Die WithTimeout-Funktion ähnelt WithDeadline, außer dass sie die verbleibende Lebensdauer des Kontexts von nun an übergibt. Beide geben auch die Kontrolle über den erstellten untergeordneten Kontext zurück, eine Funktionsvariable vom Typ CancelFunc.

Wenn die Anforderungsfunktion der obersten Ebene endet, können wir einen bestimmten Kontext abbrechen, und die Nachkommen-Goroutine beurteilt das Ende anhand von select ctx.Done ().

(4)MitWert

Geben Sie mit der Value-Funktion eine Kopie des übergeordneten Elements zurück. Durch Aufrufen der Value(key)-Methode dieser Kopie wird der Wert abgerufen. Auf diese Weise behalten wir nicht nur den ursprünglichen Wert des Wurzelknotens bei, sondern fügen auch neue Werte zu den untergeordneten Knoten hinzu. Beachten Sie, dass derselbe Schlüssel überschrieben wird, wenn er vorhanden ist.

3. Beispiele

package main
import (
        "context"        
        "fmt"        
        "time"
)
func main() {
        ctxWithCancel, cancel := context.WithTimeout(context.Background(), 5 * time.Second)                
        
        go worker(ctxWithCancel, "[1]")        
        go worker(ctxWithCancel, "[2]")                
        
        go manager(cancel)                
        
        <-ctxWithCancel.Done()        
        // 暂停1秒便于协程的打印输出        
        time.Sleep(1 * time.Second)        
        fmt.Println("example closed")
}
func manager(cancel func( )) {
        time.Sleep(10 * time.Second)         
        fmt.Println("manager called cancel()")         
        cancel() 
}                
func worker(ctxWithCancle context.Context, name string) {
        for {
                 select {                 
                 case <- ctxWithCancel.Done():                          
                          fmt.Println(name, "return for ctxWithCancel.Done()")                          
                          return                 
                 default:
                          fmt.Println(name, "working")                 
                          }                 
                          time.Sleep(1 * time.Second)        
        }
}

Das Architekturdiagramm des Kontexts dieses Prozesses:

[1]working
[2]working
[2]working
[1]working
[1]working
[2]working
[2]working
[1]working
[1]working
[2]working
[1]return for ctxWithCancel.Done()
[2]return for ctxWithCancel.Done()example closed

Es ist ersichtlich, dass das Ende des Workers dieses Mal durch den Ablauf des Timers von ctxWithCancel verursacht wird.

Ändern Sie die Dauer des Managers auf 2 Sekunden, lassen Sie die Dauer von WithTimeout unverändert und führen Sie es erneut aus. Der Worker arbeitete nur 2 Sekunden, bevor er vom Manager im Voraus gestoppt wurde.

[1]working
[2]working
[2]working
[1]workingmanager called cancel()
[1]return for ctxWithCancel.Done()
[2]return for ctxWithCancel.Done()example closed

Ich denke du magst

Origin blog.csdn.net/ZNBase/article/details/131410274
Empfohlen
Rangfolge