프로그래밍시니어 Go 개발자

Go에서 defer 함수는 어떻게 작동합니까: 어떻게 호출되고, 어떤 순서로 실행되며, 사용 시 어떤 세부사항을 고려해야 합니까?

Hintsage AI 어시스턴트로 면접 통과

답변.

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 함수는 항상 LIFO(Last In, First Out) 순서로 실행됩니다 — 마지막에 선언된 함수가 가장 먼저 호출됩니다.
  • defer의 인자는 즉시 계산되지만 함수 자체는 나중에 실행됩니다.
  • 함수가 패닉으로 종료되더라도 모든 defer가 호출됩니다.

함정 질문.

함수 내에서 패닉이 발생하면 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를 사용하여 리소스의 지연 해제를 초래 (예: DB 연결)
  • defer의 변수는 변경 불가능하다고 완전히 확신하는 것 — 하지만 그 값은 즉시 고정됩니다.
  • 무거운 리소스의 지연 해제를 수동 호출 대신 너무 늦게 수행하는 것.

실제 사례

부정적인 사례

루프에서 천 개의 파일을 열고 각 파일에 대해 defer를 사용하는 경우 모든 파일은 함수의 마지막에만 닫히며, 리소스는 지연되어 "누수" — 열린 파일의 한도를 초과하게 됩니다.

장점:

  • 코드가 간결함
  • 어떤 경우에도 해제를 보장함

단점:

  • 함수가 완료될 때까지 리소스 누수 발생
  • defer와 관련된 대량 작업에서 오류 발생

긍정적인 사례

루프 내에서 로컬 함수를 사용하며, defer는 이 파일 범위 내에서만 적용되고 전체 핸들러에 적용되지 않는 경우:

for _, name := range fileNames { func() { f, _ := os.Open(name) defer f.Close() // f로 작업 }() }

장점:

  • 리소스를 즉시 반환함
  • 누수 없음

단점:

  • 읽기가 더 어려워짐 (추가 함수 중첩)
  • defer의 범위를 기억해야 함