defer는 주변 함수의 종료 시점까지 함수의 실행을 연기합니다 — 패닉 또는 리턴에 의해 종료가 이루어지더라도 말이죠. 루프 내에서 defer가 사용되면 모든 연기된 호출이 defer 호출 스택에 쌓이고 주변 함수가 종료될 때 역순으로 실행됩니다.
이로 인해 예상치 못한 자원 소모와 지연이 발생할 수 있습니다. 모든 연기된 함수는 함수 본문이 종료된 후에만 동시에 호출되며, 루프의 각 반복 후에는 호출되지 않습니다.
코드 예제:
func readFiles(files []string) { for _, name := range files { f, _ := os.Open(name) defer f.Close() // 자원은 전체 함수가 종료될 때까지 해제되지 않음 // 파일 처리 ... } }
이 예제에서는 파일들이 함수 실행이 끝날 때까지 열려 있어, 많은 파일이 있을 경우 디스크립터 누수가 발생할 수 있습니다.
루프 내에서 자원 닫기를 위해 defer를 사용하면 어떤 일이 발생합니까? 왜 항상 최적이 아닐까요?
답변: 모든 defer 호출이 쌓이고 함수 종료 후에만 실행되어, 자원(예: 열려 있는 파일)이 너무 늦게 해제됩니다.
올바른 방법:
for _, name := range files { f, _ := os.Open(name) // defer f.Close() // 안돼요! // 올바르게: process(f) f.Close() }
이야기
로그를 업로드하는 프로젝트에서 문제가 발생했습니다: 서비스가 갑자기 새로운 파일을 열지 못하게 되었습니다. 파일 개수는 적었지만 문제는 루프 내 defer 때문이었습니다. 모든 파일이 열리면서 닫기는 함수 종료까지 연기되었습니다. 처리 후 명시적으로 Close()를 호출하자 문제가 해결되었습니다.
이야기
대량의 목록에 대한 메트릭을 수집하는 서비스에서 데이터 순환 내에서 데이터베이스에 대한 연결을 종료하기 위해 defer가 사용되었습니다. 반복 횟수가 증가하면서 지연과 열려 있는 연결 수를 초과하게 되어 'too many open connections' 오류로 서비스가 중단되었습니다.
이야기
엔지니어는 "우아한" 클리닝을 기대하며 많은 파일을 읽는 루프 내에서 defer를 적용했으며, 이로 인해 프로덕션 서버에서 열려 있는 디스크립터 수 제한이 초과되어 서비스가 중단되었습니다.