이번 게시글에서는 golang의 defer에 대해 정리하겠습니다.
defer는 사용하여 패닉을 복구, lock unlock, 스트림닫기, 채널닫기, context 종료 등 여러곳에서 사용됩니다.
defer의 사용법과 defer를 활용해 panic을 제어하는 예제입니다.
1. 지연실행
defer는 지연실행을 요청하는 키워드입니다.
스코프가 끝난 이후 defer키워드가 붙은 메서드를 실행합니다.
package main
import (
"fmt"
)
func main() {
defer func() {
fmt.Println("스코프가 끝난이후 실행")
}()
fmt.Println("메인스코프 종료")
}
$ go run main.go
메인스코프 종료
스코프가 끝난이후 실행
2. lock
package main
import (
"fmt"
"sync"
"time"
)
var (
mu = sync.Mutex{}
)
func main() {
go func() { hello() }()
go func() { hello() }()
time.Sleep(time.Second * 3)
}
func hello() {
mu.Lock()
defer mu.Unlock()
fmt.Println("hello world")
time.Sleep(time.Second * 1)
}
두개의 고루틴이 hello world를 출력하는 예제입니다.
각각의 고루틴이 hello world를 동시에 출력하지 못하게 lock을 잡아놨습니다.
(하나의 고루틴이 hello world를 출력하고 1초의 sleep 이후 unlock)
Unlock을 sleep 이후 직접 호출해도 되지만 스코프가 끝난후 defer에서 실행하게 할 수도 있습니다.
3. try, catch, finally
자바, .NET 진형은 try catch finally가 존재합니다.
그러나 golang은 error 인터페이스로 예외처리를 하며
try, catch, finally가 존재하지 않습니다.
대신 콜스택에 쌓여버리는 예외는 defer를 사용하여 제어할 수 있습니다.
(1) try, finally
목표1: 패닉이 나더라도 열어놓은 파일스트림을 닫기
목표2: 패닉이 났어도 메인스코프 종료라는 문자열을 출력
package main
import (
"fmt"
"os"
)
func main() {
myOpenFile()
fmt.Println("메인스코프 종료")
}
func myOpenFile() {
resource, err := os.Open("1.txt")
defer func() {
fmt.Println("무슨일이 생겨도 defer가 존재하는 스코프가 끝났다면 이 로직을 진행")
if resource != nil {
resource.Close() // 무슨일이 생겨도 스트림은 잘 닫힙니다.
}
}()
if err != nil {
panic(err) // 1.txt는 안만들어놨으니 패닉발생
}
// panic 발생시 아랫줄들은 실행 안됨
fmt.Println("1.txt를 열었습니다.")
bytes := make([]byte, 1024)
resource.Read(bytes)
fmt.Println(bytes)
fmt.Print("myOpenFile 스코프 종료")
}
$ go run main.go
무슨일이 생겨도 defer가 존재하는 스코프가 끝났다면 이 로직을 진행
panic: open 1.txt: The system cannot find the file specified.
goroutine 1 [running]:
main.myOpenFile()
C:/Users/a/development/go/src/defer-tut/main.go:23 +0x265
main.main()
C:/Users/a/development/go/src/defer-tut/main.go:9 +0x29
exit status 2
panic이 발생했지만 defer 키워드로 지연동작을 시켜놓은 로직은 정상적으로 진행되었습니다.
스트림을 여는경우 자바 .NET진형에서는 finally에서 안전하게 스트림을 닫아준다면
golang에서는 해당방식을 사용하여 안전하게 Close를 해줍니다.
그러나 유심히 보면 메인스코프 종료라는 메시지가 출력되지 않았습니다.
콜스택에 익셉션(panic)이 쌓여서 프로그램은 터져버렸습니다.
(2) catch
defer는 finally처럼 꼭 실행해야되는 로직을 실행시킬 수도 있지만
catch처럼 예외처리를 할 수 있습니다.
(panic을 복구시키고 예외를 잡아올 수 있습니다)
package main
import (
"fmt"
"os"
"github.com/pkg/errors"
)
func main() {
err := myOpenFile()
if err != nil {
fmt.Println(err.Error())
}
fmt.Println("메인스코프 종료")
}
func myOpenFile() (err error) {
resource, err := os.Open("1.txt")
defer func() {
fmt.Println("무슨일이 생겨도 defer가 존재하는 스코프가 끝났다면 이 로직을 진행")
if resource != nil {
resource.Close()
}
// 아래에서 패닉이 발생되었지만,
// 여기서 recover 키워드로 프로그램이 죽지 않게합니다.
if r := recover(); r != nil {
err = errors.Errorf("패닉이 발생하였지만 복구하였습니다 에러메시지: %v", r)
}
}()
if err != nil {
panic(err)
}
fmt.Println("1.txt를 열었습니다.")
bytes := make([]byte, 1024)
resource.Read(bytes)
fmt.Println(bytes)
fmt.Print("myOpenFile 스코프 종료")
return nil
}
$ go run main.go
무슨일이 생겨도 defer가 존재하는 스코프가 끝났다면 이 로직을 진행
패닉이 발생하였지만 복구하였습니다 에러메시지: open 1.txt: The system cannot find the file specified.
메인스코프 종료
이번에는 프로그램이 죽지 않았습니다.
panic이 발생할만한 스코프에서 defer 키워드를 사용하여 스코프가 다 종료된후
recover 키워드를 사용해 발생한 panic을 확인하고 복구시켜줍니다.
결과적으로 메인스코프 종료라는 메시지가 정상적으로 출력된 것을 확인할 수 있습니다.
golang은 try finally(finally 내부에서 catch)라고 생각하시면 될것 같습니다.
'golang' 카테고리의 다른 글
[golang] golang 채널(channel) 사용법, 사용예제 (0) | 2021.12.06 |
---|---|
[golang] 고루틴(go routine)이란 (0) | 2021.11.15 |
[golang] go언어를 객체지향언어처럼 사용하는방법 (0) | 2021.08.16 |
[golang] go언어의 의존성 주입(di)과 제어의 역전(ioc) (0) | 2021.08.15 |
[golang] go언어 예외처리 방법 (0) | 2021.08.15 |
댓글