> 백엔드 개발 > Golang > Go에서 defer란 무엇인가요? 그것을 사용하는 방법?

Go에서 defer란 무엇인가요? 그것을 사용하는 방법?

藏色散人
풀어 주다: 2021-09-01 15:52:43
앞으로
2578명이 탐색했습니다.

이 글은 go 언어튜토리얼 칼럼에서 소개한 글입니다. Go Defer를 배우고 활용하는 방법이 필요한 친구들에게 도움이 되었으면 좋겠습니다!

Go에서 함수 호출은 defer 키워드 뒤에 와서 지연된 함수 호출을 형성할 수 있습니다. 함수 호출이 지연되면 즉시 실행되지 않습니다. 현재 코루틴에 의해 유지 관리되는 지연된 호출 스택으로 푸시됩니다. 함수 호출(지연된 호출일 수도 있고 아닐 수도 있음)이 반환되어 종료 단계에 들어갈 때 이 함수 호출 내에서 푸시된 모든 지연된 호출은 스택에 푸시된 순서의 역순으로 실행됩니다. . 이러한 지연된 호출이 모두 실행되면 함수 호출이 실제로 종료됩니다. 간단한 예:

package mainimport "fmt"func sum(a, b int) {
    defer fmt.Println("sum函数即将返回")
    defer fmt.Println("sum函数finished")
    fmt.Printf("参数a=%v,参数b=%v,两数之和为%v\n", a, b, a+b)}func main() {
    sum(1, 2)}
로그인 후 복사
출력:

参数a=1,参数b=2,两数之和为3
sum函数finished
sum函数即将返回
로그인 후 복사
defer关键字后面,形成一个延迟函数调用。
当一个函数调用被延迟后,它不会立即被执行。它将被推入由当前协程维护的一个延迟调用堆栈。 当一个函数调用(可能是也可能不是一个延迟调用)返回并进入它的退出阶段后,所有在此函数调用中已经被推入的延迟调用将被按照它们被推入堆栈的顺序逆序执行。 当所有这些延迟调用执行完毕后,此函数调用也就真正退出了。
举个简单的例子:
package mainimport  "fmt"func  Print(a int) {fmt.Println("defer函数中a的值=", a)}func  main() {a := 10defer  Print(a)a = 1000fmt.Println("a的值=", a)}
로그인 후 복사

output:

a的值= 1000
defer函数中a的值= 10
로그인 후 복사

事实上,每个协程维护着两个调用堆栈。

  • 一个是正常的函数调用堆栈。在此堆栈中,相邻的两个调用存在着调用关系。晚进入堆栈的调用被早进入堆栈的调用所调用。 此堆栈中最早被推入的调用是对应协程的启动调用。
  • 另一个堆栈是上面提到的延迟调用堆栈。处于延迟调用堆栈中的任意两个调用之间不存在调用关系。

defer函数参数估值

  • 对于一个延迟函数调用,它的实参是在此调用被推入延迟调用堆栈的时候被估值的。
  • 一个匿名函数体内的表达式是在此函数被执行的时候才会被逐个估值的,不管此函数是被普通调用还是延迟调用。
    例子1:
package mainimport "fmt"func main() {
    func() {
        for i := 0; i < 3; i++ {
            defer fmt.Println("a=", i)
        }
    }()

    fmt.Println()
    func() {
        for i := 0; i < 3; i++ {
            defer func() {
                fmt.Println("b=", i)
            }()
        }
    }()}
로그인 후 복사

output:

a= 2
a= 1
a= 0

b= 3
b= 3
b= 3
로그인 후 복사

defer Print(a) 被加入到延迟调用堆栈的时候,a 的值是5,故defer Print(a) 输出的结果为5
例子2:

package mainimport "fmt"func main() {
    func() {
        for i := 0; i < 3; i++ {
            defer fmt.Println("a=", i)
        }
    }()

    fmt.Println()
    func() {
        for i := 0; i < 3; i++ {
            defer func(i int) {
                fmt.Println("b=", i)
            }(i)
        }
    }()}
로그인 후 복사

output:

a= 2
a= 1
a= 0

b= 2
b= 1
b= 0
로그인 후 복사

第一个匿名函数循环中的 i 是在 fmt.Println函数调用被推入延迟调用堆栈的时候估的值,因此输出结果是 2,1,0 , 第二个匿名函数中的 i 是匿名函数调用退出阶段估的值(此时 i 已经变成3了),故结果输出:3,3,3。
其实对第二个匿名函数调用略加修改,就能使它输出和匿名函数一相同的结果:

package mainimport (
    "fmt"
    "time")func p(a, b int) int {
    return a / b}func main() {
    go func() {
        fmt.Println(p(1, 0))
    }()
    time.Sleep(time.Second)
    fmt.Println("程序正常退出~~~")}
로그인 후 복사

output:

panic: runtime error: integer pide by zero

goroutine 6 [running]:
main.p(...)
        /Users/didi/Desktop/golang/defer.go:9
main.main.func1()
        /Users/didi/Desktop/golang/defer.go:14 +0x12
created by main.main
        /Users/didi/Desktop/golang/defer.go:13 +0x39exit status 2
로그인 후 복사

恐慌(panic)和恢复(defer + recover)

Go不支持异常抛出和捕获,而是推荐使用返回值显式返回错误。 不过,Go支持一套和异常抛出/捕获类似的机制。此机制称为恐慌/恢复(panic/recover)机制。

我们可以调用内置函数panic来产生一个恐慌以使当前协程进入恐慌状况。

进入恐慌状况是另一种使当前函数调用开始返回的途径。 一旦一个函数调用产生一个恐慌,此函数调用将立即进入它的退出阶段,在此函数调用中被推入堆栈的延迟调用将按照它们被推入的顺序逆序执行。

通过在一个延迟函数调用之中调用内置函数recover실제로 각 코루틴은 두 개의 호출 스택을 유지합니다.

  • 하나는 일반적인 함수 호출 스택입니다. 이 스택에는 인접한 두 호출 사이에 호출 관계가 있습니다. 스택에 늦게 배치된 호출은 스택에 일찍 배치된 호출에 의해 호출됩니다. 이 스택에 푸시된 가장 이른 호출은 해당 코루틴의 시작 호출입니다.
  • 또 다른 스택은 위에서 언급한 지연된 호출 스택입니다. 지연된 호출 스택의 두 호출 간에는 호출 관계가 없습니다.

함수 매개변수 평가 연기


  • 지연된 함수 호출의 경우 호출이 지연된 호출 스택에 푸시될 때 실제 매개변수가 평가됩니다.
  • 익명 함수 본문의 표현식은 함수가 정상적으로 호출되었는지 지연되었는지에 관계없이 함수가 실행될 때 하나씩 평가됩니다. 예제 1:
package mainimport (
    "fmt"
    "time")func p(a, b int) int {
    return a / b}func main() {
    go func() {
        defer func() {
            v := recover()
            if v != nil {
                fmt.Println("恐慌被恢复了:", v)
            }
        }()
        fmt.Println(p(1, 0))
    }()
    time.Sleep(time.Second)
    fmt.Println("程序正常退出~~~")}
로그인 후 복사

output:

恐慌被恢复了: runtime error: integer pide by zero
程序正常退出~~~
로그인 후 복사

defer Print(a)가 지연된 호출 스택에 추가되고 a의 값은 5이므로 defer Print(a)의 출력 결과는 다음과 같습니다. 5 예제 2:rrreeeoutput:rrreeei 첫 번째 익명 함수 루프의 fmt.Println 함수 호출이 지연된 호출 스택에 푸시될 때 추정된 값이므로 출력 결과는 2, 1, 0, 두 번째 익명 함수의 i는 익명 함수 호출의 종료 단계에서 추정된 값(이때 i는 3이 됨)이므로 결과 출력은 3, 3, 3입니다. 사실 두 번째 익명 함수 호출을 약간 수정하면 익명 함수 호출과 동일한 결과를 출력할 수 있습니다.
rrreee

output:🎜rrreee🎜🎜🎜🎜패닉 및 복구(지연 + 복구)🎜 🎜🎜Go 에서는 예외 발생 및 잡기를 지원하지 않지만 오류를 명시적으로 반환하려면 반환 값을 사용하는 것이 좋습니다. 그러나 Go는 예외 던지기/잡기와 유사한 메커니즘을 지원합니다. 이 메커니즘을 패닉/복구 메커니즘이라고 합니다. 🎜🎜내장 함수 panic을 호출하여 현재 코루틴을 패닉 상태로 만드는 패닉을 생성할 수 있습니다. 🎜🎜패닉 상태에 들어가는 것은 현재 함수 호출이 반환을 시작하는 또 다른 방법입니다. 함수 호출이 패닉을 생성하면 함수 호출은 즉시 종료 단계로 들어가고, 함수 호출 내의 스택에 푸시된 지연된 호출은 푸시된 순서의 역순으로 실행됩니다. 🎜🎜지연된 함수 호출에서 내장 함수 recover를 호출하면 현재 코루틴의 패닉이 제거되어 현재 코루틴이 다시 정상 상태로 돌아갈 수 있습니다. 🎜🎜패닉 상태의 코루틴이 종료되기 전에 패닉 상태는 다른 코루틴으로 확산되지 않습니다. 코루틴이 패닉 상태에서 종료되면 전체 프로그램이 중단됩니다. 다음 두 가지 예를 살펴보세요. 🎜rrreee🎜output:🎜rrreee🎜p 함수 패닉(제수는 0). 코루틴에 패닉 복구 메커니즘이 없어 전체 프로그램이 충돌하기 때문입니다. 🎜p 함수가 위치한 코루틴에 패닉 복구(defer+recover) 기능을 추가하면 프로그램이 정상적으로 종료될 수 있습니다. 🎜rrreee🎜output:🎜rrreee🎜🎜더 많은 golang 관련 지식을 보려면 🎜🎜golang🎜🎜튜토리얼 칼럼을 방문하세요!

위 내용은 Go에서 defer란 무엇인가요? 그것을 사용하는 방법?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:learnku.com
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿