Maison >développement back-end >Golang >pointeur de fonction golang invalide
Dans Golang, les pointeurs de fonction sont un type important. Leurs principales utilisations incluent les fonctions de rappel, le chargement dynamique des fonctions de bibliothèque, etc. Par conséquent, leur échec aura un impact sérieux sur l'exactitude du programme.
Cependant, dans les environnements de développement réels, nous rencontrons souvent des situations où les pointeurs de fonction deviennent invalides, provoquant d'étranges erreurs dans le programme. Cet article prendra un cas spécifique comme exemple pour analyser les raisons de l'échec du pointeur de fonction et expliquer comment éviter cette situation.
Analyse de cas
Dans un service API GraphQL, j'ai utilisé une bibliothèque tierce pour analyser les instructions de requête GraphQL et utiliser des fonctions personnalisées pour traiter certains champs. Cette bibliothèque fournit un type de pointeur de fonction, il suffit d'y passer la fonction personnalisée.
Plus précisément, l'extrait de code est le suivant :
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 }
La méthode AddFieldResolver est utilisée pour ajouter un pointeur de fonction résolu par champ à la structure du type Resolver. Parallèlement, cette structure de type Resolver implémente également une interface GraphQL Resolver.
Dans mon implémentation, j'ai ajouté deux pointeurs de fonction de résolution de champ à cette structure de type Resolver. Ces deux pointeurs de fonction sont déterminés respectivement par les deux noms de champ name etcreated_at.
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, } }
L'initialisation de ces deux pointeurs de fonction de résolution de champ s'effectue dans la méthode initFieldResolvers, qui sera appelée dans le constructeur de la structure de type Resolver.
Nous devons également implémenter l'interface spécifique du Resolver dans la structure du type Resolver. La méthode spécifique est la suivante :
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 }
Cela appelle dynamiquement le pointeur de fonction d'analyse que nous avons enregistré auparavant, c'est-à-dire les deux fonctions name et create_at.
Cependant, pendant le processus de test, j'ai constaté que cette implémentation était très instable. Parfois, elle pouvait fonctionner normalement, mais à d'autres moments, elle signalait une erreur "résolveur introuvable".
Analyse des causes
Pour cette situation, j'ai d'abord pensé à la possibilité que le pointeur de fonction devienne invalide. Dans le développement réel, nous rencontrons souvent des problèmes similaires : stocker le pointeur de fonction dans une variable de structure, puis appeler la valeur de cette variable ailleurs, pour constater que le pointeur a expiré.
Dans le langage Go, comme dans d'autres langages, les fonctions sont des citoyens de première classe et les pointeurs de fonction sont également stockés sous forme de variables. Dans des circonstances normales, le pointeur de fonction n'expirera pas. En théorie, ce qui est stocké dans la mémoire est une adresse de pointeur qui peut être appelée, et le pointeur ne sera recyclé qu'à la fin du programme.
Cependant, dans notre scénario, puisque cette structure de type Resolver est partagée entre plusieurs coroutines, il existe des accès simultanés, et il existe également des situations où la coroutine sort pour libérer de la mémoire. Cela peut rendre le pointeur de fonction invalide.
Solution
Résoudre le problème de l'échec du pointeur de fonction consiste essentiellement à éviter l'échec du pointeur. Dans la programmation simultanée de Golang, il existe des moyens techniques pour garantir que certaines données ne se tromperont pas lors d'un accès simultané. Ensuite, nous présenterons deux techniques courantes pour éviter les pointeurs de fonction non valides.
Pour la structure de type Resolver dans le code ci-dessus, nous pouvons utiliser le type sync.RWMutex pour protéger la lecture et l'écriture simultanées du champ fieldResolvers. De cette façon, nous pouvons garantir qu'aucune condition de concurrence ne se produira lors de la lecture des champs fieldResolvers.
Dans le même temps, nous pouvons également utiliser une tranche de pointeurs de fonction au lieu du type de carte Lors de la lecture du pointeur de fonction, il n'est pas nécessaire d'accéder au type de carte, évitant ainsi l'apparition de conditions de concurrence.
Le code spécifique est le suivant :
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) }
Ici, j'ai changé le type de fieldResolvers de map[string]fieldResolver à []*fieldResolver. L’utilisation de types de pointeurs évite l’allocation de mémoire redondante et la copie de données.
Une autre astuce pour éviter les pointeurs de fonction invalides consiste à stocker les pointeurs de fonction dans les canaux. Plus précisément, lorsque nous devons appeler un pointeur de fonction, nous pouvons envoyer le pointeur de fonction au canal et attendre dans une autre coroutine que le canal renvoie le pointeur de fonction avant de passer l'appel.
De cette façon, il peut être garanti que le pointeur de fonction n'expirera pas et des problèmes tels que la libération de mémoire lors de la sortie de la coroutine peuvent être évités.
Le code spécifique est le suivant :
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 } } }() }
Ici, j'ai changé le type de fieldResolvers en chan *fieldResolver, et j'ai appelé le pointeur de fonction dans ce canal via une coroutine.
Conclusion
Pour le problème d'invalidation du pointeur de fonction rencontré dans Golang, nous devons prêter attention aux problèmes de concurrence et de libération de mémoire du programme. Pour éviter les conditions de concurrence et les problèmes de gestion de la mémoire, nous pouvons profiter des puissantes fonctionnalités de programmation simultanée de Golang, telles que RWMutex et chan.
Dans le même temps, nous devons également prêter attention à l'utilisation de types de pointeurs et éviter les allocations de mémoire et la copie de données inutiles afin de réduire autant que possible la probabilité de défaillance du pointeur de fonction.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!