프로그래밍Senior Go 개발자

Go에서 전역 변수를 다룰 때의 특징은 무엇이며, 잘못된 초기화 및 실행 시간 경쟁(init order)을 피하려면 어떻게 해야 합니까? 일반적인 함정은 무엇입니까?

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

답변.

Go에서 전역 변수는 파일의 선언 순서와 각 패키지의 init 함수 순서에 따라 초기화됩니다. 이는 전역 상태에 대한 접근의 정확성에 영향을 미칩니다: 하나의 패키지의 변수가 다른 패키지의 변수 값에 의존하지만, 해당 패키지가 아직 초기화되지 않았다면 — 오류가 발생하거나 명백하지 않은 동작이 생길 수 있습니다.

init 함수를 가진 서로 다른 패키지 간의 '시작 순서'를 직접 지정할 수는 없습니다. 컴파일러는 import 그래프를 바탕으로 이 순서를 결정합니다.

  • 모든 전역 변수main.main() 함수가 호출되기 전에 초기화됩니다.
  • 패키지 init 중에 과도한 종속성을 가진 상태의 초기화를 수행하는 것은 권장하지 않습니다!
  • 전역 변수의 스레드 안전성을 위해 복잡한 초기화 시 sync.Once 또는 mutex를 사용합니다.

안전한 지연 초기화(lazy init)의 예:

var cfg *Config var once sync.Once func GetConfig() *Config { once.Do(func() { cfg = LoadConfigFromDisk() }) return cfg }

함정 질문.

질문: 서로 다른 패키지의 init 함수가 Go 프로그램 시작 시 동시에 실행될 수 있습니까?

정답: 아니요, init 함수와 전역 변수는 패키지 간의 종속성 분석에 따라 컴파일러에 의해 엄격하게 순차적으로 실행됩니다. init 함수의 병렬 실행은 발생하지 않습니다.


주제의 미세한 이해 부족으로 인한 실제 오류 사례.


이야기

큰 프로젝트에서 여러 모듈이 init()를 통해 디스크에서 전역 설정을 불러왔습니다. 한 모듈이 다른 모듈에 의존했지만, go.mod 그래프 재구성으로 인해 초기화 순서가 변경되었고, 갑자기 설정 값이 비어 있었습니다! 오류는 빌드 순서에 따라 우연히 발생했으며, 종속성을 분석하고 초기화를 명시적인 함수로 바꾼 후에야 밝혀졌습니다.


이야기

REST 서비스에서 전역 map을 mutex 없이 캐싱용으로 사용했습니다. 여러 goroutine에서 init 중에 초기화가 시작되었습니다. 결과적으로 — 데이터 레이스, 주기적인 패닉, map 내부의 잘못된 데이터가 발생했습니다. sync.Once로 교체하고 초기화 호출을 명시적으로 한 후 문제는 사라졌습니다.


이야기

전역 로거가 보조 패키지의 init 함수에서 생성되었으나, 다른 패키지가 초기화되기 전에 시작 시 이 로거에 접근했습니다. 결국, 로거가 존재하지 않는 동안 일부 시작 로그가 사라졌습니다. 해결책은 의존성 주입 및 모든 서비스 시작 전에 로거를 명시적으로 초기화하는 것이었습니다.