본문 바로가기

Language/Go

[Go] defer, panic, recover??

Go 코드를 보다보면 다른 언어와 다르게 defer, panic, recover가 자주등장한다. defer는 키워드로, panic, recover는 내장함수로 지원되는데 어떻게 사용되는지 알아보자

 

1. defer

2. panic

3. recover

 

 

defer

- defer를 사용하면 list에 함수 호출을 넣는다.

- 이러한 list는 주변 함수가 리턴된 이후에 실행된다. (after surrounding function returns)

 

Example

- 파일을 복사하는 경우 아래와 같이 src,dst 파일의 열고 닫는 과정이 필요하다.

- 이 때, dst 파일의 os.Create호출에서 문제가 발생했을 때, 우리는 src파일의 Close하는 논리과정을 한 번 더 작성해야한다. (아래의 코드는 버그 발생의 가능성이 있다!)

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }

    written, err = io.Copy(dst, src)
    dst.Close()
    src.Close()
    return
}

 

- 하지만 아래와 같이 src, dst 파일을 열고, 생성하자마자 defer함수로 CLose 해준다면 defer 구문에 넣은 함수 호출을 나중에 실행하기 떄문에 새로운 논리를 작성할 필요가 없다. (The files will be closed)

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()

    return io.Copy(dst, src)
}

 

 

* defer 구문을 사용할 때 3개의 규칙이 있다. :

1. A deferred function's arguments are evaluated when the defer statement is evaluated.

2. Deferred function calls are executed in Last In First Out order after the surrounding function returns

3. Deferred functions may read and assign to the returning function's named return values

 

 

1. defer에 넣는 함수의 인자는 defer가 사용될 때 평가 된다.

 

Example

- a 함수의 fmt.Println(i)가 defer로 함수 호출 list에 푸시 될 때, 그 함수의 실제 호출을 i++ 이후가 된다. (after surrounding function returns)

- 하지만 그 인자(i)는 defer로 함수 호출이 푸시 될 때 평가 되어서, i++하기 이전의 값을 출력하여 a 함수는 "0"을 출력하는 함수가 된다.) 

func a() {
    i := 0
    defer fmt.Println(i)
    i++
    return
}

 

2. defer로 넣는 함수 호출은 LIFO, 즉 stack에 들어가 실행된다.

 

Example

- b 함수는 fmt.Print 함수 호출을 4번 defer로 넣어 실행한다.

- 이 경우 fmt.Print 함수 호출은 stack에 들어가 LIFO 순서로 실행된다. 따라서 아래의 결과는 "3210"이 된다.

func b() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i)
    }
}

 

 

3. defer로 넣은 함수들은 반환하는 함수의 named return value을 읽고, 할당할 수 있다.

 

Example

- c함수는 int형의 i를 return한다. 따라서 c함수는 1을 retrun하고, 그 값을 다시 defer된 함수에서 read and assign하여 c 함수는 "2"를 return 한다.

func c() (i int) {
    defer func() { i++ }()
    return 1
}

- Error return value를 수정할 떄 편한다고 하는데... 아직 3번의 경우 감이 잘 안온다.

 

 

Panic

- Panic is a built-in function that stops the odinary flow of control and begins panicking

- When the function F calls panic, execution of F stops, any deferred functions in F are executed normally and then F returns to its caller

- To the caller, F then behaves like a call to panic.

The process continues up the stack until all functions in the current goroutine have returned, at which point the program crashes.

- Panics can be initiated by invoking panic directly. They can also be caused by runtime errors, such as out-of-bounds array accesses.

 

- panic을 호출하면 정상 flow를 멈추고 panicking에 빠지게 된다.

- 어떤 함수 F가 panicking에 빠지면, F는 중지되고 F에 있는 deferred function들이 실행되고 F는 호출자에게 반환한다.

- 호출자의 입장에서는, F는 panic을 호출한 것 처럼 행동한다.

- 이러한 과정은 현재 고루틴에 존재하는 모든 함수가 리턴될 때 까지 스택으로 진행된다. (at which point the program crashes..?)

 

Recover

- Recover is a built-in functions that regains control of panicking goroutine. Recover is only useful inside deferred functions.

-  During normal execution, a call to recover will return nil and have no other effect.

- if the current goroutine is panicking, a call to recover will capture the value given to panic and resume normal execution.

 

- Recover는 panic에 빠진 고루틴의 제어를 다시 얻는 것이며, deferred function 안에서만 유용하다.

- 정상 실행상태에서는, recover함수는 nil을 반환하고 아무런 영향이 없다.

- 만약 현재 고루틴이 panic에 빠지면, recover 함수는 panic에 빠질 때의 변수를 capture하고 정상 실행을 계속한다.

 

 

Example

- g는 int형 i를 입력받아 그 값이 3보다 크면, panic을 실행하고, 그렇지 않으면 값을 1 증가시키며 재귀호출한다.

- f는 revocer를 호출하고 recover된 값을 출력하는 함수를 defer하고 g함수를 실행한다.

package main

import "fmt"

func main() {
    f()
    fmt.Println("Returned normally from f.")
}

func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
}

func g(i int) {
    if i > 3 {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i + 1)
}

- 한 번 결과를 상상해보자.

 

 

실행결과

- f는 g(0)을 호출한다. g는 i가 3보다 크기 이전 값을 출력하는 함수를 defer하고, 그 값을 바로 출력한다. (Printing in g, i) 만약, i가 4가 된다면, g 내부의 panic함수가 실행되고, return 되면서 g 내부의 deferred function들을 호출한다.(stack) g 함수에서 panic을 발생했기 떄문에, 그 에 대한 caller (f)는 g가 panic을 한 것처럼 행동하기 때문에, f도 defer된 recover함수를 실행하게 되고, 최초 초과된 값 4를 recover하여 return 한다.

 

 

내장 라이브러리 코드를 보고 실제 사용에 대해 공부해보자!

- https://golang.org/src/encoding/json/encode.go

 

 

** reference

https://blog.golang.org/defer-panic-and-recover