>백엔드 개발 >Golang >Go의 진입점 뒤편 살펴보기 - 초기화부터 종료까지

Go의 진입점 뒤편 살펴보기 - 초기화부터 종료까지

Linda Hamilton
Linda Hamilton원래의
2024-12-17 22:10:11828검색

A Peek Behind Go’s Entry Point - From Initialization to Exit

Go를 처음 시작하면 주요 기능이 너무 단순해 보일 정도입니다. 단일 진입점, 간단하게 main.go 실행 및 짜잔 - 우리 프로그램이 실행 중입니다.

하지만 더 깊이 파고들면서 커튼 뒤에는 미묘하고 세심하게 계획된 과정이 있다는 것을 깨달았습니다. 메인이 시작되기 전에 Go 런타임은 가져온 모든 패키지의 초기화를 신중하게 조정하고 초기화 기능을 실행하며 모든 것이 올바른 순서로 되어 있는지 확인합니다. 혼란스러운 상황은 허용되지 않습니다.

Go가 정렬하는 방식에는 모든 Go 개발자가 알아야 할 깔끔한 세부 사항이 있습니다. 이는 코드 구조화, 공유 리소스 처리, 심지어 오류를 시스템에 다시 전달하는 방식에 영향을 미치기 때문입니다.

메인 킥이 시작되기 전과 후에 실제로 무슨 일이 일어나고 있는지 강조하는 몇 가지 일반적인 시나리오와 질문을 살펴보겠습니다.


메인 이전: 순차적 초기화 및 init의 역할

이렇게 생각해 보세요. 각각 고유한 초기화 기능이 있는 여러 패키지가 있습니다. 그 중 하나는 데이터베이스 연결을 구성하고, 다른 하나는 일부 로깅 기본값을 설정하고, 세 번째는 람다 작업자를 초기화하고, 네 번째는 SQS 대기열 수신기를 초기화할 수 있습니다.

메인 실행 시간에는 모든 것이 준비되어 있어야 합니다. 절반 초기화된 상태나 마지막 순간의 놀라움은 없어야 합니다.

예: 여러 패키지 및 주문 초기화

// db.go
package db

import "fmt"

func init() {
    fmt.Println("db: connecting to the database...")
    // Imagine a real connection here
}

// cache.go
package cache

import "fmt"

func init() {
    fmt.Println("cache: warming up the cache...")
    // Imagine setting up a cache here
}

// main.go
package main

import (
    _ "app/db"   // blank import for side effects
    _ "app/cache"
    "fmt"
)

func main() {
    fmt.Println("main: starting main logic now!")
}

이 프로그램을 실행하면 다음이 표시됩니다.

db: connecting to the database...
cache: warming up the cache...
main: starting main logic now!

데이터베이스가 먼저 초기화되고(mainimports db 이후) 캐시가 마지막으로 메시지를 인쇄합니다. Go는 가져온 모든 패키지가 메인 실행 전에 초기화되도록 보장합니다. 이 종속성 기반 순서가 핵심입니다. 캐시가 db에 의존하는 경우 캐시 초기화가 실행되기 전에 db가 설정을 완료했을 것입니다.

특정 초기화 순서 보장

이제 캐시 전에 반드시 dinitialized가 필요하거나 그 반대의 경우에는 어떻게 될까요? 자연스러운 접근 방식은 캐시가 db에 의존하는지 확인하거나 기본에서 db 이후에 가져오는 것입니다. Go는 main.go에 나열된 가져오기 순서가 아닌 종속성 순서대로 패키지를 초기화합니다. 우리가 사용하는 트릭은 빈 가져오기입니다: _ "path/to/package" - 특정 패키지를 강제로 초기화합니다. 하지만 나는 빈 가져오기를 기본 방법으로 사용하지 않을 것입니다. 종속성이 덜 명확해지고 유지 관리 문제가 발생할 수 있습니다.

대신 초기화 순서가 종속성에 따라 자연스럽게 나타나도록 패키지를 구성하는 것이 좋습니다. 이것이 가능하지 않다면 초기화 논리가 컴파일 시 엄격한 순서에 의존해서는 안 됩니다. 예를 들어 sync.Once 또는 유사한 패턴을 사용하여 런타임 시 db가 준비되었는지 캐시 검사를 수행할 수 있습니다.

순환 종속성 방지

초기화 수준의 순환 종속성은 Go에서 절대 금기 사항입니다. 패키지 AB를 가져오고 BA를 가져오려고 하면 순환 종속성이 생성된 것입니다. . Go는 컴파일을 거부하여 혼란스러운 런타임 문제의 세계에서 여러분을 구해줍니다. 엄격하게 느껴질 수도 있지만 저를 믿으세요. 런타임에 이상한 초기화 상태를 디버깅하는 것보다 이러한 문제를 조기에 발견하는 것이 더 좋습니다.

공유 리소스 및 sync.Once 처리

AB 패키지가 모두 공유 리소스(예: 구성 파일 또는 전역 설정 개체)에 의존하는 시나리오를 상상해 보세요. 둘 다 initfunctions를 갖고 있으며 둘 다 해당 리소스를 초기화하려고 시도합니다. 리소스가 한 번만 초기화되도록 어떻게 보장하나요?

일반적인 해결책은 sync.Once 호출 뒤에 공유 리소스 초기화를 배치하는 것입니다. 이렇게 하면 여러 패키지가 이를 트리거하더라도 초기화 코드가 정확히 한 번만 실행됩니다.

예: 단일 초기화 보장

// db.go
package db

import "fmt"

func init() {
    fmt.Println("db: connecting to the database...")
    // Imagine a real connection here
}

// cache.go
package cache

import "fmt"

func init() {
    fmt.Println("cache: warming up the cache...")
    // Imagine setting up a cache here
}

// main.go
package main

import (
    _ "app/db"   // blank import for side effects
    _ "app/cache"
    "fmt"
)

func main() {
    fmt.Println("main: starting main logic now!")
}

이제 가져오기 구성의 패키지 수에 관계없이 someValue의 초기화는 한 번만 발생합니다. 패키지 A와 B가 모두 config.Value()에 의존하는 경우 둘 다 적절하게 초기화된 값을 볼 수 있습니다.

단일 파일 또는 패키지의 여러 초기화 함수

동일한 파일에 여러 개의 init 함수가 있을 수 있으며 표시된 순서대로 실행됩니다. 동일한 패키지의 여러 파일에서 Go는 일관되지만 엄격하게 정의되지 않은 순서로 init 함수를 실행합니다. 컴파일러는 파일을 알파벳순으로 처리할 수 있지만 이에 의존해서는 안 됩니다. 코드가 동일한 패키지 내의 특정 init 함수 시퀀스에 의존하는 경우 이는 종종 리팩토링하라는 신호입니다. 초기화 논리를 최소화하고 긴밀한 결합을 피하세요.

적법한 사용과 안티 패턴

init 함수는 간단한 설정(데이터베이스 드라이버 등록, 명령줄 플래그 초기화 또는 로거 설정)에 가장 적합합니다. 복잡한 논리, 장기간 실행되는 I/O 또는 합당한 이유 없이 패닉이 발생할 수 있는 코드는 다른 곳에서 더 잘 처리됩니다.

경험상, init에 많은 로직을 작성하고 있다면 해당 로직을 main에서 명시적으로 만드는 것을 고려해 볼 수 있습니다.

은혜롭게 종료하고 os.Exit() 이해하기

Go의 메인은 값을 반환하지 않습니다. 외부 세계에 오류를 알리고 싶다면 os.Exit()가 도움이 됩니다. 하지만 명심하세요: os.Exit()를 호출하면 프로그램이 즉시 종료됩니다. 지연된 기능이 실행되지 않으며 패닉 스택 추적이 인쇄되지 않습니다.

예: 퇴실 전 정리

db: connecting to the database...
cache: warming up the cache...
main: starting main logic now!

정리 호출을 건너뛰고 바로 os.Exit(1)으로 점프하면 리소스를 적절하게 정리할 기회를 잃게 됩니다.

프로그램을 종료하는 다른 방법

패닉 상태로 인해 프로그램이 종료될 수도 있습니다. 지연된 함수에서 Recover()에 의해 복구되지 않은 패닉은 프로그램을 중단시키고 스택 추적을 인쇄합니다. 이는 디버깅에 편리하지만 일반적인 오류 신호에는 적합하지 않습니다. os.Exit()와 달리 패닉은 프로그램이 끝나기 전에 지연된 함수를 실행할 수 있는 기회를 제공하여 정리에 도움이 될 수 있지만 깨끗한 종료 코드를 기대하는 최종 사용자나 스크립트에게는 덜 깔끔해 보일 수도 있습니다.

Cmd C의 SIGINT와 같은 신호도 프로그램을 종료할 수 있습니다. 군인이라면 신호를 잡아서 우아하게 처리할 수 있습니다.


런타임, 동시성 및 주요 고루틴

고루틴이 시작되기 전에 초기화가 이루어지므로 시작 시 경쟁 조건이 발생하지 않습니다. 그러나 메인이 시작되면 원하는 만큼 고루틴을 실행할 수 있습니다.

main 함수 자체는 Go 런타임에 의해 시작된 특별한 "메인 고루틴"에서 실행된다는 점에 유의하는 것이 중요합니다. main이 반환되면, 다른 고루틴이 여전히 작업을 수행 중이더라도 전체 프로그램이 종료됩니다.

이것은 일반적인 문제입니다. 백그라운드 고루틴을 시작했다고 해서 프로그램이 계속 활성 상태로 유지되는 것은 아닙니다. 메인이 끝나면 모든 것이 종료됩니다.

// db.go
package db

import "fmt"

func init() {
    fmt.Println("db: connecting to the database...")
    // Imagine a real connection here
}

// cache.go
package cache

import "fmt"

func init() {
    fmt.Println("cache: warming up the cache...")
    // Imagine setting up a cache here
}

// main.go
package main

import (
    _ "app/db"   // blank import for side effects
    _ "app/cache"
    "fmt"
)

func main() {
    fmt.Println("main: starting main logic now!")
}

이 예에서 고루틴은 메인이 종료되기 전에 3초를 기다리기 때문에 메시지만 인쇄합니다. 메인이 더 빨리 종료되면 프로그램은 고루틴이 완료되기 전에 종료됩니다. 런타임은 메인이 종료될 때 다른 고루틴을 "기다리지" 않습니다. 논리에서 특정 작업이 완료될 때까지 기다려야 하는 경우 WaitGroup 또는 채널과 같은 동기화 프리미티브를 사용하여 백그라운드 작업이 완료될 때 신호를 보내는 것을 고려해 보세요.

초기화 중에 패닉이 발생하면 어떻게 되나요?

초기화 중에 패닉이 발생하면 전체 프로그램이 종료됩니다. 메인도 없고 회복 기회도 없습니다. 디버깅에 도움이 되는 패닉 메시지가 표시됩니다. 이것이 제가 초기화 함수를 단순하고 예측 가능하게 유지하고 예기치 않게 폭발할 수 있는 복잡한 논리가 없도록 노력하는 이유 중 하나입니다.


마무리

메인이 실행될 때까지 Go는 이미 눈에 보이지 않는 수많은 발품 작업을 수행했습니다. 모든 패키지를 초기화하고 모든 init 함수를 실행하며 주변에 숨어 있는 불쾌한 순환 종속성이 없는지 확인했습니다. 이 프로세스를 이해하면 애플리케이션의 시작 순서를 더 잘 제어하고 확신할 수 있습니다.

뭔가 문제가 발생하면 깔끔하게 종료하는 방법과 지연된 함수에 어떤 일이 발생하는지 알 수 있습니다. 코드가 더욱 복잡해지면 해킹에 의존하지 않고 초기화 순서를 적용하는 방법을 알게 됩니다. 그리고 동시성이 발생하면 경쟁 조건은 init 실행 전이 아니라 실행 후에 시작된다는 것을 알 수 있습니다.

저에게는 이러한 통찰력으로 인해 단순해 보이는 Go의 주요 기능이 우아한 빙산의 일각처럼 느껴졌습니다. 여러분만의 비법이나, 우연히 부딪힌 함정이 있거나, 이러한 내부 요소에 대한 질문이 있다면 듣고 싶습니다.

결국 우리 모두는 아직 배우는 중입니다. 그게 Go 개발자가 되는 재미의 절반입니다.


읽어주셔서 감사합니다! 코드가 함께하길 바랍니다 :)

내 소셜 링크: LinkedIn | GitHub | ? (이전 트위터) | 서브스택 | Dev.to

더 많은 콘텐츠를 보려면 다음을 고려하세요. 또 봐요!

위 내용은 Go의 진입점 뒤편 살펴보기 - 초기화부터 종료까지의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.