Go 1.24에서의 Weak Pointer 이해
Lukas Schneider
DevOps Engineer · Leapcell

Go에서 weak pointer(약한 포인터)는 가비지 컬렉터(GC)가 대상 객체를 회수하는 것을 막지 않는 참조를 의미합니다.
객체가 weak pointer에 의해서만 참조되고 strong reference(강한 참조)가 없으면, GC는 여전히 이를 도달 불가능한 것으로 취급하고 회수합니다. 그 후, 해당 객체에 대한 모든 weak pointer는 자동으로 nil
이 됩니다.
요약하자면, weak pointer는 객체의 참조 횟수를 증가시키지 않습니다. 객체가 weak pointer에 의해서만 참조될 경우, 가비지 컬렉터는 이를 해제할 수 있습니다. 따라서 weak pointer의 값을 사용하기 전에 nil
인지 확인해야 합니다.
Go 1.24의 weak
패키지
Go 1.24는 weak pointer를 생성하고 사용하는 간결한 API를 제공하는 weak
패키지를 도입했습니다.
import "weak" type MyStruct struct { Data string } func main() { obj := &MyStruct{Data: "example"} wp := weak.Make(obj) // weak pointer 생성 val := wp.Value() // strong reference 또는 nil 반환 if val != nil { fmt.Println(val.Data) } else { fmt.Println("객체가 가비지 컬렉션되었습니다.") } }
위 예제에서 weak.Make(obj)
는 obj
에 대한 weak pointer를 생성합니다.
wp.Value()
를 호출하면 객체가 여전히 살아있는 경우 strong reference를 반환하고, 그렇지 않으면 nil
을 반환합니다.
Weak Pointer 테스트
import ( "fmt" "runtime" "weak" ) type MyStruct struct { Data string } func main() { obj := &MyStruct{Data: "test"} wp := weak.Make(obj) obj = nil // strong reference 제거 runtime.GC() if wp.Value() == nil { fmt.Println("객체가 가비지 컬렉션되었습니다.") } else { fmt.Println("객체가 아직 살아있습니다.") } }
strong reference obj
를 nil
로 설정하고 명시적으로 GC를 트리거함으로써,
객체가 수집되면 weak pointer가 nil
을 반환하는 것을 관찰할 수 있습니다.
Weak Pointer와 Strong Reference의 차이점
가비지 컬렉션(GC)에 미치는 영향:
- Strong reference는 객체를 살아있게 유지합니다.
- Weak reference는 객체를 살아있게 유지하지 않습니다.
Nil 값:
- Strong reference는 명시적으로 아무것도 가리키지 않을 때
nil
입니다. - Weak reference는 일반적으로 대상 객체가 이미 수집되었거나 아직 할당되지 않았기 때문에
nil
입니다.
접근 방법:
- Strong reference는 객체를 직접 역참조할 수 있습니다.
- Weak reference는 객체에 접근하기 전에
Value()
메서드를 호출해야 합니다.
예제 1: 임시 캐싱을 위한 Weak Pointer 사용
weak pointer의 일반적인 시나리오는 GC에 의해 수집되는 것을 막지 않으면서 캐시에 항목을 저장하는 것입니다.
package main import ( "fmt" "runtime" "sync" "weak" ) type User struct { Name string } var cache sync.Map // map[int]weak.Pointer[*User] func GetUser(id int) *User { // ① 먼저 캐시에서 가져오려고 시도 if wp, ok := cache.Load(id); ok { if u := wp.(weak.Pointer[User]).Value(); u != nil { fmt.Println("캐시 적중") return u } } // ② 실제로 로드 (여기서는 구성만 함) u := &User{Name: fmt.Sprintf("user-%d", id)} cache.Store(id, weak.Make(u)) fmt.Println("DB에서 로드") return u } func main() { _ = GetUser(1) // DB에서 로드 //fmt.Println(u.Name) // 'u'가 이 시점에서 범위 내에 있지 않기 때문에 주석 처리 runtime.GC() // GC가 즉시 실행되더라도 `main`이 strong reference를 보유하므로 User는 살아남습니다. //u = nil // 마지막 strong reference 해제 runtime.GC() // GC를 트리거하면 User가 수집될 수 있습니다. _ = GetUser(1) // 수집된 경우 DB에서 다시 로드합니다. }
이 캐시 구현에서 항목은 weak pointer로 저장됩니다. 객체에 다른 strong reference가 없으면 GC는 이를 회수할 수 있습니다. GetUser
를 다음에 호출하면 데이터가 다시 로드됩니다.
위 코드를 실행하면 다음 출력이 생성됩니다.
$ go run cache.go load from DB load from DB
왜 Weak Pointer를 사용해야 할까요?
일반적인 시나리오는 다음과 같습니다.
- 캐싱: 객체를 메모리에 남아있게 강제하지 않고 저장합니다. 다른 곳에서 사용되지 않으면 수집할 수 있습니다.
- Observer 패턴: 수집되는 것을 막지 않으면서 observer에 대한 참조를 유지합니다.
- 정규화: 사용되지 않는 객체를 수집할 수 있도록 하면서 동일한 객체의 인스턴스가 하나만 존재하도록 합니다.
- 종속성 그래프: 트리 또는 그래프와 같은 구조에서 참조 사이클을 방지합니다.
Weak Pointer 사용에 대한 참고 사항
- 항상 nil을 확인하십시오: 객체는 모든 GC 주기에서 수집될 수 있습니다.
Value()
의 결과를 캐시해서는 안 됩니다. - 순환 종속성을 피하십시오: weak pointer로 참조되는 객체가 컨테이너에 대한 strong reference를 다시 보유하지 않도록 하십시오. 그렇지 않으면 사이클이 여전히 형성될 수 있습니다.
- 성능 절충: weak pointer에 접근하려면 추가 호출이 필요하고,
nil
로 돌아가는 객체를 자주 다시 로드하면 변동이 발생할 수 있습니다.
예제 2: Strong Pointer의 일반적인 사용
package main import ( "fmt" "runtime" ) type Session struct { ID string } func main() { s := new(Session) // &Session{}과 동일 s.ID = "abc123" fmt.Println("strong ref alive:", s.ID) s = nil // 마지막 strong reference 제거 runtime.GC() // GC를 트리거하려고 시도 (데모 용도로만, 실제 타이밍은 런타임에 따라 다름) fmt.Println("완료") }
여기서 s
는 strong pointer입니다. 이 포인터가 reachable 상태로 유지되는 한, Session
객체는 GC에 의해 수집되지 않습니다.
Strong Pointer가 가리키는 객체는 언제 수집되나요?
-
도달 가능성 분석: Go는 mark-and-sweep GC를 사용합니다. GC 주기 시작 시, 런타임은 루트 객체(스택, 전역 변수, 현재 레지스터 등)에서 모든 strong reference를 탐색합니다.
- Strong reference를 통해 도달할 수 있는 객체는 _도달 가능_한 것으로 간주되어 살아있게 됩니다.
- 다른 모든 객체는 _도달 불가능_한 것으로 표시되어 스윕 단계에서 해제됩니다.
-
참조 횟수 없음: Go는 객체가 루트에서 도달 가능한지 여부만 확인합니다. 참조 횟수나 값이 같은지 여부는 수집에 영향을 미치지 않습니다.
-
불확실한 타이밍: GC 주기는 스케줄러에 의해 자동으로 트리거됩니다. 개발자는
runtime.GC()
를 _힌트_로 호출할 수 있지만, 즉시 수집을 보장하지는 않습니다. -
변수 자체가 수집될 수 있음: strong pointer 변수
s
가 힙에 상주하고 이를 보유하는 구조체가 더 이상 도달 가능하지 않으면,s
자체가 수집될 수 있습니다. 스택 변수는 함수가 반환될 때 해제됩니다.
요약
Strong pointer는 이 포인터가 가리키는 객체가 모든 GC 주기에서 "라이브 세트"에 남아 있음을 보장합니다. 마지막 strong reference가 끊어지면, 해당 객체는 다음 GC 주기 동안 자동으로 해제됩니다.
Leapcell은 Go 프로젝트 호스팅을 위한 최고의 선택입니다.
Leapcell은 웹 호스팅, 비동기 작업 및 Redis를 위한 차세대 서버리스 플랫폼입니다.
다국어 지원
- Node.js, Python, Go 또는 Rust로 개발하십시오.
무제한 프로젝트를 무료로 배포하세요
- 사용량에 대해서만 지불하세요. 요청도 없고, 요금도 없습니다.
탁월한 비용 효율성
- 유휴 요금 없이 사용한 만큼 지불하세요.
- 예: $25는 평균 응답 시간 60ms에서 694만 건의 요청을 지원합니다.
간소화된 개발자 경험
- 간편한 설정을 위한 직관적인 UI.
- 완전 자동화된 CI/CD 파이프라인 및 GitOps 통합.
- 실행 가능한 통찰력을 위한 실시간 메트릭 및 로깅.
간편한 확장성 및 고성능
- 쉬운 고 동시성을 처리하기 위한 자동 스케일링.
- 제로 운영 오버헤드 - 구축에만 집중하십시오.
설명서에서 자세히 알아보세요!
X에서 우리를 팔로우하세요: @LeapcellHQ