Maison  >  Article  >  développement back-end  >  Golang Defer : différence allouée au tas, allouée à la pile, à code ouvert

Golang Defer : différence allouée au tas, allouée à la pile, à code ouvert

WBOY
WBOYoriginal
2024-08-07 18:10:56268parcourir

Ceci est un extrait du post ; l'article complet est disponible ici : Golang Defer : From Basic To Trap.

L'instruction defer est probablement l'une des premières choses que nous trouvons assez intéressantes lorsque nous commençons à apprendre le Go, n'est-ce pas ?

Mais il y a bien plus encore qui font trébucher beaucoup de gens, et il y a de nombreux aspects fascinants que nous n'abordons souvent pas lorsque nous l'utilisons.

Golang Defer: Heap-allocated, Stack-allocated, Open-coded Defer

Allocation par tas, allocation par pile, report à code ouvert

Par exemple, l'instruction defer a en fait 3 types (à partir de Go 1.22, bien que cela puisse changer plus tard) : defer à code ouvert, defer alloué au tas et alloué à la pile. Chacun a des performances différentes et différents scénarios dans lesquels il est préférable de les utiliser, ce qui est bon à savoir si vous souhaitez optimiser les performances.

Dans cette discussion, nous allons tout aborder, des bases à l'utilisation plus avancée, et nous approfondirons même un peu, juste un petit peu, certains détails internes.

Qu’est-ce que le report ?

Jetons un rapide coup d'œil au report avant de plonger trop profondément.

Dans Go, defer est un mot-clé utilisé pour retarder l'exécution d'une fonction jusqu'à ce que la fonction environnante se termine.

func main() {
  defer fmt.Println("hello")
  fmt.Println("world")
}

// Output:
// world
// hello

Dans cet extrait, l'instruction defer planifie que fmt.Println("hello") soit exécuté à la toute fin de la fonction principale. Ainsi, fmt.Println("world") est appelé immédiatement et "world" est imprimé en premier. Après cela, parce que nous avons utilisé le report, "bonjour" est imprimé comme dernière étape avant la fin principale.

C'est comme configurer une tâche à exécuter plus tard, juste avant la fin de la fonction. Ceci est vraiment utile pour les actions de nettoyage, comme fermer une connexion à une base de données, libérer un mutex ou fermer un fichier :

func doSomething() error {
  f, err := os.Open("phuong-secrets.txt")
  if err != nil {
    return err
  }
  defer f.Close()

  // ...
}

Le code ci-dessus est un bon exemple pour montrer comment fonctionne le report, mais c'est aussi une mauvaise façon d'utiliser le report. Nous y reviendrons dans la section suivante.

"D'accord, bien, mais pourquoi ne pas mettre le f.Close() à la fin ?"

Il y a plusieurs bonnes raisons à cela :

  • Nous plaçons l'action de fermeture à proximité de l'action d'ouverture, il est donc plus facile de suivre la logique et d'éviter d'oublier de fermer le fichier. Je ne veux pas faire défiler une fonction pour vérifier si le fichier est fermé ou non ; cela me distrait de la logique principale.
  • La fonction différée est appelée lorsque la fonction revient, même si une panique (erreur d'exécution) se produit.

En cas de panique, la pile est déroulée et les fonctions différées sont exécutées dans un ordre spécifique, que nous aborderons dans la section suivante.

Les reports sont empilés

Lorsque vous utilisez plusieurs instructions différées dans une fonction, elles sont exécutées dans un ordre « pile », ce qui signifie que la dernière fonction différée est exécutée en premier.

func main() {
  defer fmt.Println(1)
  defer fmt.Println(2)
  defer fmt.Println(3)
}

// Output:
// 3
// 2
// 1

Chaque fois que vous appelez une instruction defer, vous ajoutez cette fonction en haut de la liste chaînée de la goroutine actuelle, comme ceci :

Golang Defer: Heap-allocated, Stack-allocated, Open-coded Defer

Chaîne de report Goroutine

Et lorsque la fonction revient, elle parcourt la liste chaînée et exécute chacune d'elles dans l'ordre indiqué dans l'image ci-dessus.

Mais rappelez-vous, il n'exécute pas tous les defers dans la liste chaînée de goroutine, il exécute uniquement le defer dans la fonction renvoyée, car notre liste chaînée defer pourrait contenir de nombreux différés de nombreuses fonctions différentes.

func B() {
  defer fmt.Println(1)
  defer fmt.Println(2)
  A()
}

func A() {
  defer fmt.Println(3)
  defer fmt.Println(4)
}

Ainsi, seules les fonctions différées dans la fonction actuelle (ou le cadre de pile actuel) sont exécutées.

Golang Defer: Heap-allocated, Stack-allocated, Open-coded Defer

Chaîne de report Goroutine

Mais il existe un cas typique où toutes les fonctions différées de la goroutine actuelle sont tracées et exécutées, et c'est à ce moment-là qu'une panique se produit.

Différer, paniquer et récupérer

Outre les erreurs de compilation, nous avons un tas d'erreurs d'exécution : division par zéro (entier uniquement), hors limites, déréférencement d'un pointeur nul, etc. Ces erreurs provoquent la panique de l'application.

La panique est un moyen d'arrêter l'exécution de la goroutine actuelle, de dérouler la pile et d'exécuter les fonctions différées dans la goroutine actuelle, provoquant le crash de notre application.

Pour gérer les erreurs inattendues et éviter que l'application ne plante, vous pouvez utiliser la fonction de récupération au sein d'une fonction différée pour reprendre le contrôle d'une goroutine paniquée.

func main() {
  defer func() {
    if r := recover(); r != nil {
      fmt.Println("Recovered:", r)
    }
  }()

  panic("This is a panic")
}

// Output:
// Recovered: This is a panic

Habituellement, les gens mettent une erreur dans la panique et l'attrapent avec recovery(..), mais cela peut être n'importe quoi : une chaîne, un int, etc.

In the example above, inside the deferred function is the only place you can use recover. Let me explain this a bit more.

There are a couple of mistakes we could list here. I’ve seen at least three snippets like this in real code.

The first one is, using recover directly as a deferred function:

func main() {
  defer recover()

  panic("This is a panic")
}

The code above still panics, and this is by design of the Go runtime.

The recover function is meant to catch a panic, but it has to be called within a deferred function to work properly.

Behind the scenes, our call to recover is actually the runtime.gorecover, and it checks that the recover call is happening in the right context, specifically from the correct deferred function that was active when the panic occurred.

"Does that mean we can’t use recover in a function inside a deferred function, like this?"

func myRecover() {
  if r := recover(); r != nil {
    fmt.Println("Recovered:", r)
  }
}

func main() {
  defer func() {
    myRecover()
    // ...
  }()

  panic("This is a panic")
}

Exactly, the code above won’t work as you might expect. That’s because recover isn’t called directly from a deferred function but from a nested function.

Now, another mistake is trying to catch a panic from a different goroutine:

func main() {
  defer func() {
    if r := recover(); r != nil {
      fmt.Println("Recovered:", r)
    }
  }()

  go panic("This is a panic")

  time.Sleep(1 * time.Second) // Wait for the goroutine to finish
}

Makes sense, right? We already know that defer chains belong to a specific goroutine. It would be tough if one goroutine could intervene in another to handle the panic since each goroutine has its own stack.

Unfortunately, the only way out in this case is crashing the application if we don’t handle the panic in that goroutine.

Defer arguments, including receiver are immediately evaluated

I've run into this problem before, where old data got pushed to the analytics system, and it was tough to figure out why.

Here’s what I mean:

func pushAnalytic(a int) {
  fmt.Println(a)
}

func main() {
  a := 10
  defer pushAnalytic(a)

  a = 20
}

What do you think the output will be? It's 10, not 20.

That's because when you use the defer statement, it grabs the values right then. This is called "capture by value." So, the value of a that gets sent to pushAnalytic is set to 10 when the defer is scheduled, even though a changes later.

There are two ways to fix this.

...

Full post is available here: Golang Defer: From Basic To Trap.

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!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn