Heim  >  Artikel  >  Backend-Entwicklung  >  Golang-Funktionszeiger ungültig

Golang-Funktionszeiger ungültig

王林
王林Original
2023-05-10 17:11:37502Durchsuche

In Golang sind Funktionszeiger ein wichtiger Typ. Zu ihren Hauptverwendungszwecken gehören Rückruffunktionen, dynamisches Laden von Bibliotheksfunktionen usw. Daher hat ihr Ausfall schwerwiegende Auswirkungen auf die Korrektheit des Programms.

In realen Entwicklungsumgebungen kommt es jedoch häufig zu Situationen, in denen Funktionszeiger ungültig werden, was zu seltsamen Fehlern im Programm führt. In diesem Artikel wird ein konkreter Fall als Beispiel verwendet, um die Gründe für einen Funktionszeigerfehler zu analysieren und zu erörtern, wie diese Situation vermieden werden kann.

Fallanalyse

In einem GraphQL-API-Dienst habe ich eine Bibliothek eines Drittanbieters verwendet, um GraphQL-Abfrageanweisungen zu analysieren und benutzerdefinierte Funktionen zu verwenden, um einige der Felder zu verarbeiten. Diese Bibliothek stellt einen Funktionszeigertyp bereit. Wir müssen lediglich die benutzerdefinierte Funktion daran übergeben.

Im Einzelnen lautet der Codeausschnitt wie folgt:

type fieldResolver func(ctx context.Context, obj interface{}, args map[string]interface{}) (interface{}, error)
type Resolver struct {
    // ...
    fieldResolvers map[string]fieldResolver
    // ...
}

func (r *Resolver) AddFieldResolver(fieldName string, fr fieldResolver) {
    r.fieldResolvers[fieldName] = fr
}

Die AddFieldResolver-Methode wird verwendet, um einen feldaufgelösten Funktionszeiger zur Resolver-Typstruktur hinzuzufügen. Gleichzeitig implementiert diese Resolver-Typstruktur auch eine GraphQL-Resolver-Schnittstelle.

In meiner Implementierung habe ich dieser Resolver-Typstruktur zwei Feldauflösungsfunktionszeiger hinzugefügt. Diese beiden Funktionszeiger werden durch die beiden Feldnamen name bzw.created_at bestimmt.

func (r *Resolver) BaseQuery() QueryResolver {
    return &queryResolver{r}
}
func (r *Resolver) name(ctx context.Context, obj interface{}, f graphql.ResolveFieldParams) (interface{}, error) {
    // handler for name field
}

func (r *Resolver) created_at(ctx context.Context, obj interface{}, f graphql.ResolveFieldParams) (interface{}, error) {
    // handler for created_at field
}

func (r *Resolver) initFieldResolvers() {
    r.fieldResolvers = map[string]fieldResolver{
        "name":          r.name,
        "created_at":    r.created_at,
    }
}

Die Initialisierung dieser beiden Feldauflösungsfunktionszeiger wird in der Methode initFieldResolvers abgeschlossen, die im Konstruktor der Resolver-Typstruktur aufgerufen wird.

Wir müssen auch die spezifische Resolver-Schnittstelle in der Resolver-Typstruktur implementieren. Die spezifische Methode lautet wie folgt:

type queryResolver struct{ *Resolver }

func (r *queryResolver) Name(ctx context.Context, obj *types.User) (string, error) {
    resolver, ok := r.fieldResolvers["name"]
    if !ok {
        return "", fmt.Errorf("resolver not found")
    }
    result, err := resolver(ctx, obj, nil)
    if err != nil {
        return "", err
    }
    return result.(string), nil
}

func (r *queryResolver) CreatedAt(ctx context.Context, obj *types.User) (string, error) {
    resolver, ok := r.fieldResolvers["created_at"]
    if !ok {
        return "", fmt.Errorf("resolver not found")
    }
    result, err := resolver(ctx, obj, nil)
    if err != nil {
        return "", err
    }
    return result.(string), nil
}

Dies ruft dynamisch den zuvor registrierten Analysefunktionszeiger auf, dh die beiden Funktionen name undcreated_at.

Während des Testvorgangs stellte ich jedoch fest, dass diese Implementierung sehr instabil war. Manchmal konnte sie normal funktionieren, manchmal wurde jedoch der Fehler „Resolver nicht gefunden“ gemeldet.

Ursachenanalyse

In dieser Situation dachte ich zuerst an die Möglichkeit, dass der Funktionszeiger ungültig wird. In der tatsächlichen Entwicklung stoßen wir häufig auf ähnliche Probleme: Wir speichern den Funktionszeiger in einer Strukturvariablen und rufen dann den Wert dieser Variablen an einer anderen Stelle auf, nur um dann festzustellen, dass der Zeiger abgelaufen ist.

In der Go-Sprache sind Funktionen wie in anderen Sprachen erstklassige Bürger, und Funktionszeiger werden auch als Variablen gespeichert. Unter normalen Umständen läuft der Funktionszeiger nicht ab. Theoretisch wird im Speicher eine Zeigeradresse gespeichert, die aufgerufen werden kann, und der Zeiger wird erst am Ende des Programms wiederverwendet.

Da diese Struktur vom Typ Resolver in unserem Szenario jedoch von mehreren Coroutinen gemeinsam genutzt wird, gibt es gleichzeitige Zugriffe und es gibt auch Situationen, in denen die Coroutine beendet wird, um Speicher freizugeben. Dies kann dazu führen, dass der Funktionszeiger ungültig wird.

Lösung

Die Lösung des Problems des Funktionszeigerfehlers besteht im Wesentlichen darin, Zeigerfehler zu vermeiden. In der gleichzeitigen Programmierung von Golang gibt es einige technische Mittel, um sicherzustellen, dass beim gleichzeitigen Zugriff auf bestimmte Daten keine Fehler auftreten. Als Nächstes stellen wir zwei gängige Techniken zur Vermeidung ungültiger Funktionszeiger vor.

  1. Vermeiden Sie Datenkonkurrenz durch Kopieren der Struktur

Für die Resolver-Typstruktur im obigen Code können wir den Typ sync.RWMutex verwenden, um das gleichzeitige Lesen und Schreiben des fieldResolvers-Felds zu schützen. Auf diese Weise können wir sicherstellen, dass beim Lesen von fieldResolvers-Feldern keine Race Conditions auftreten.

Gleichzeitig können wir anstelle des Kartentyps auch einen Funktionszeigerausschnitt verwenden. Beim Lesen des Funktionszeigers ist kein Zugriff auf den Kartentyp erforderlich, wodurch das Auftreten von Race Conditions vermieden wird.

Der spezifische Code lautet wie folgt:

type Resolver struct {
    sync.RWMutex
    fieldResolvers []*fieldResolver
}

func (r *Resolver) AddFieldResolver(fr *fieldResolver) {
    r.Lock()
    defer r.Unlock()

    r.fieldResolvers = append(r.fieldResolvers, fr)
}

func (r *Resolver) name(ctx context.Context, obj interface{}, f graphql.ResolveFieldParams) (interface{}, error) {
    // handler for name field
}

func (r *Resolver) created_at(ctx context.Context, obj interface{}, f graphql.ResolveFieldParams) (interface{}, error) {
    // handler for created_at field
}

func (r *Resolver) initFieldResolvers() {
    r.AddFieldResolver(&r.name)
    r.AddFieldResolver(&r.created_at)
}

Hier habe ich den Typ von fieldResolvers von „map[string]fieldResolver“ in „[]*fieldResolver“ geändert. Durch die Verwendung von Zeigertypen werden redundante Speicherzuweisungen und das Kopieren von Daten vermieden.

  1. Funktionszeiger in Kanälen speichern

Ein weiterer Trick, um ungültige Funktionszeiger zu vermeiden, besteht darin, Funktionszeiger in Kanälen zu speichern. Wenn wir insbesondere einen Funktionszeiger aufrufen müssen, können wir den Funktionszeiger an den Kanal senden und in einer anderen Coroutine darauf warten, dass der Kanal den Funktionszeiger zurückgibt, bevor wir den Aufruf durchführen.

Auf diese Weise kann sichergestellt werden, dass der Funktionszeiger nicht abläuft, und Probleme wie die Speicherfreigabe beim Beenden der Coroutine können vermieden werden.

Der spezifische Code lautet wie folgt:

type Resolver struct {
    fieldResolvers chan *fieldResolver
    // ...
}

func (r *Resolver) AddFieldResolver(fr *fieldResolver) {
    r.fieldResolvers <- fr
}

func (r *Resolver) initFieldResolvers() {
    // ...
    go func() {
        for fr := range r.fieldResolvers {
            if fr != nil {
                // call the function pointer
            }
        }
    }()
}

Hier habe ich den Typ von fieldResolvers in chan *fieldResolver geändert und den Funktionszeiger in diesem Kanal über eine Coroutine aufgerufen.

Fazit

Für das in Golang auftretende Problem der Ungültigmachung von Funktionszeigern müssen wir auf die Parallelitäts- und Speicherfreigabeprobleme des Programms achten. Um Race Conditions und Speicherverwaltungsprobleme zu vermeiden, können wir die leistungsstarken gleichzeitigen Programmierfunktionen von Golang wie RWMutex und Chan nutzen.

Gleichzeitig müssen wir auch auf die Verwendung von Zeigertypen achten und unnötige Speicherzuweisung und Datenkopien vermeiden, um die Wahrscheinlichkeit eines Funktionszeigerausfalls so weit wie möglich zu reduzieren.

Das obige ist der detaillierte Inhalt vonGolang-Funktionszeiger ungültig. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn