"Se um trabalhador quiser fazer bem o seu trabalho, ele deve primeiro afiar suas ferramentas." - Confúcio, "Os Analectos de Confúcio. Lu Linggong"
Primeira página > Programação > Golang Defer: adiamento alocado em heap, alocado em pilha e código aberto

Golang Defer: adiamento alocado em heap, alocado em pilha e código aberto

Publicado em 19/08/2024
Navegar:369

Este é um trecho da postagem; a postagem completa está disponível aqui: Golang Defer: From Basic To Trap.

A instrução defer é provavelmente uma das primeiras coisas que achamos bastante interessantes quando começamos a aprender Go, certo?

Mas há muito mais que confunde muitas pessoas, e há muitos aspectos fascinantes que muitas vezes não abordamos ao usá-lo.

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

Adiamento alocado em heap, alocado em pilha e código aberto

Por exemplo, a instrução defer na verdade tem 3 tipos (a partir do Go 1.22, embora isso possa mudar posteriormente): defer de código aberto, defer alocado em heap e alocado em pilha. Cada um tem desempenho diferente e cenários diferentes onde são melhor utilizados, o que é bom saber se você deseja otimizar o desempenho.

Nesta discussão, cobriremos tudo, desde o básico até o uso mais avançado, e até nos aprofundaremos um pouco, só um pouquinho, em alguns dos detalhes internos.

O que é adiar?

Vamos dar uma olhada rápida no adiamento antes de nos aprofundarmos muito.

Em Go, defer é uma palavra-chave usada para atrasar a execução de uma função até que a função circundante termine.

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

// Output:
// world
// hello

Neste trecho, a instrução defer agenda fmt.Println("hello") para ser executada bem no final da função principal. Portanto, fmt.Println("world") é chamado imediatamente e "world" é impresso primeiro. Depois disso, como usamos defer, "hello" é impresso como a última etapa antes do main terminar.

É como configurar uma tarefa para ser executada mais tarde, logo antes de a função terminar. Isso é realmente útil para ações de limpeza, como fechar uma conexão de banco de dados, liberar um mutex ou fechar um arquivo:

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

  // ...
}

O código acima é um bom exemplo para mostrar como o defer funciona, mas também é uma maneira ruim de usar o defer. Veremos isso na próxima seção.

"Ok, ótimo, mas por que não colocar f.Close() no final?"

Existem alguns bons motivos para isso:

  • Colocamos a ação de fechar perto da ação de abrir, assim fica mais fácil seguir a lógica e evitar esquecer de fechar o arquivo. Não quero rolar uma função para verificar se o arquivo está fechado ou não; isso me distrai da lógica principal.
  • A função adiada é chamada quando a função retorna, mesmo se ocorrer um pânico (erro de tempo de execução).

Quando ocorre um pânico, a pilha é desenrolada e as funções adiadas são executadas em uma ordem específica, que abordaremos na próxima seção.

Adiamentos estão empilhados

Quando você usa várias instruções defer em uma função, elas são executadas em uma ordem de 'pilha', o que significa que a última função adiada é executada primeiro.

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

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

Cada vez que você chama uma instrução defer, você adiciona essa função ao topo da lista vinculada da goroutine atual, assim:

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

Cadeia de adiamento de goroutine

E quando a função retorna, ela percorre a lista vinculada e executa cada uma na ordem mostrada na imagem acima.

Mas lembre-se, ele não executa todo o defer na lista vinculada da goroutine, apenas executa o defer na função retornada, porque nossa lista vinculada de defer pode conter muitos defers de muitas funções diferentes.

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

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

Portanto, apenas as funções diferidas na função atual (ou quadro de pilha atual) são executadas.

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

Cadeia de adiamento de goroutine

Mas há um caso típico em que todas as funções adiadas na goroutine atual são rastreadas e executadas, e é aí que ocorre um pânico.

Adiar, entrar em pânico e recuperar

Além dos erros em tempo de compilação, temos vários erros em tempo de execução: divisão por zero (somente número inteiro), fora dos limites, desreferenciação de um ponteiro nulo e assim por diante. Esses erros fazem com que o aplicativo entre em pânico.

Panic é uma forma de interromper a execução da goroutine atual, desenrolar a pilha e executar as funções adiadas na goroutine atual, fazendo com que nosso aplicativo trave.

Para lidar com erros inesperados e evitar que o aplicativo trave, você pode usar a função de recuperação dentro de uma função adiada para recuperar o controle de uma goroutine em pânico.

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

  panic("This is a panic")
}

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

Normalmente, as pessoas colocam um erro no pânico e pegam isso com recuperação(..), mas pode ser qualquer coisa: uma string, um int, etc.

No exemplo acima, dentro da função adiada é o único lugar onde você pode usar a recuperação. Deixe-me explicar isso um pouco mais.

Existem alguns erros que poderíamos listar aqui. Já vi pelo menos três trechos como este em código real.

A primeira é usar a recuperação diretamente como uma função adiada:

func main() {
  defer recover()

  panic("This is a panic")
}

O código acima ainda entra em pânico, e isso ocorre por design do tempo de execução Go.

A função de recuperação destina-se a capturar um pânico, mas deve ser chamada dentro de uma função adiada para funcionar corretamente.

Nos bastidores, nossa chamada para recuperação é na verdade runtime.gorecover e verifica se a chamada de recuperação está acontecendo no contexto correto, especificamente a partir da função adiada correta que estava ativa quando o pânico ocorreu.

"Isso significa que não podemos usar a recuperação em uma função dentro de uma função adiada, como esta?"

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

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

  panic("This is a panic")
}

Exatamente, o código acima não funcionará como você esperava. Isso ocorre porque a recuperação não é chamada diretamente de uma função adiada, mas de uma função aninhada.

Agora, outro erro é tentar capturar o pânico de uma goroutine diferente:

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
}

Faz sentido, certo? Já sabemos que as cadeias de defer pertencem a uma goroutine específica. Seria difícil se uma goroutine pudesse intervir em outra para lidar com o pânico, já que cada goroutine tem sua própria pilha.

Infelizmente, a única saída neste caso é travar o aplicativo se não lidarmos com o pânico naquela goroutine.

Argumentos de adiamento, incluindo o receptor, são imediatamente avaliados

Já me deparei com esse problema antes, onde dados antigos eram enviados para o sistema analítico e era difícil descobrir o porquê.

Aqui está o que quero dizer:

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

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

  a = 20
}

Qual você acha que será o resultado? São 10, não 20.

Isso ocorre porque quando você usa a instrução defer, ela captura os valores imediatamente. Isso é chamado de “captura por valor”. Portanto, o valor de a que é enviado para pushAnalytic é definido como 10 quando o adiamento é agendado, mesmo que a mude posteriormente.

Existem duas maneiras de corrigir isso.

...

A postagem completa está disponível aqui: Golang Defer: From Basic To Trap.

Declaração de lançamento Este artigo foi reproduzido em: https://dev.to/func25/golang-defer-heap-allocated-stack-allocated-open-coded-defer-1h9o?1 Se houver alguma violação, entre em contato com [email protected] para excluí-lo
Tutorial mais recente Mais>

Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.

Copyright© 2022 湘ICP备2022001581号-3