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.
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.
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:
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.
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:
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.
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.
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.
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.
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