defer는 Go에서 리소스 관리(예: 파일, 뮤텍스, 연결)를 단순화하기 위해 고안되었습니다. 이는 함수 실행의 마지막에 작업이 수행되도록 보장해야 할 때 사용됩니다. 역사적으로 비슷한 구조체는 다른 언어에서도 존재했습니다(예: Java의 finally, try-with-resources) 하지만 Go는 보다 명시적이고 이해하기 쉬운 패턴을 구현합니다.
문제: 오류가 발생하거나 패닉이 일어날 경우에도 리소스가 항상 해제되는지 확신해야 합니다. 리소스의 이중 해제 호출이나 누수는 전통적인 프로그래밍 스타일에서 흔한 문제입니다.
해결책: 함수나 메서드에서 defer로 선언된 모든 작업은 호출 스택에 쌓이고 함수가 종료되기 전에 반대 순서로 실행됩니다. 이는 예외(패닉)나 조기 반환이 있더라도 리소스가 해제됨을 보장합니다.
코드 예:
func processFile() error { f, err := os.Open("filename.txt") if err != nil { return err } defer f.Close() // 파일 닫기는 마지막에 발생 // 파일 작업 return nil }
주요 특징:
함수 내에서 패닉이 발생하면 defer가 실행됩니까?
예! 모든 defer 함수는 패닉이 발생하더라도 호출됩니다. 이는 "최종 처리"의 기본 메커니즘입니다.
defer에 전달된 함수의 인자는 언제 계산됩니까?
defer가 선언될 때 계산되며, 실제로 실행될 때가 아닙니다. 따라서 나중에 변경되는 변수를 사용할 경우 이를 고려해야 합니다:
a := 1 defer fmt.Println(a) a = 2 // 1을 출력, 2가 아님
루프 내에서 defer가 어떻게 작동합니까? 메모리 누수를 초래하지 않습니까?
루프의 각 반복에서 defer를 사용하면 모든 defer는 함수 전체가 끝난 후에만 실행됩니다. 따라서 defer 함수 스택이 쌓여서 과도한 메모리 사용이 발생할 수 있습니다.
for i := 0; i < 3; i++ { defer fmt.Println(i) }
루프에서 천 개의 파일을 열고 각 파일에 대해 defer를 사용하는 경우 모든 파일은 함수의 마지막에만 닫히며, 리소스는 지연되어 "누수" — 열린 파일의 한도를 초과하게 됩니다.
장점:
단점:
루프 내에서 로컬 함수를 사용하며, defer는 이 파일 범위 내에서만 적용되고 전체 핸들러에 적용되지 않는 경우:
for _, name := range fileNames { func() { f, _ := os.Open(name) defer f.Close() // f로 작업 }() }
장점:
단점: