Go의 빈 구조체의 무정적 사용
Grace Collins
Solutions Engineer · Leapcell

머리말
Go 프로그래밍 언어에는 많은 사람들을 혼란스럽게 할 수 있는 특별한 용법이 있습니다. 바로 빈 구조체 struct{}
입니다. 이 글에서는 Go의 빈 구조체에 대한 자세한 설명을 제공하겠습니다. 준비되셨나요? 좋아하는 음료나 차를 들고 함께 살펴봅시다.
빈 구조체란 무엇인가
필드를 포함하지 않는 구조체를 빈 구조체라고 합니다. 다음과 같은 두 가지 방법으로 정의할 수 있습니다.
- 익명 빈 구조체
var e struct{}
- 명명된 빈 구조체
type EmptyStruct struct{} var e EmptyStruct
빈 구조체의 특징
빈 구조체는 다음과 같은 주요 특징을 갖습니다.
- 제로 메모리 할당
- 동일한 주소
- 상태 없음
제로 메모리 할당
빈 구조체는 메모리 공간을 차지하지 않습니다. 이는 메모리 최적화에 매우 유용하게 만듭니다. 빈 구조체가 실제로 제로 메모리를 차지하는지 확인하기 위해 다음 예제를 살펴봅시다.
package main import ( "fmt" "unsafe" ) func main() { var a int var b string var e struct{} fmt.Println(unsafe.Sizeof(a)) // 4 fmt.Println(unsafe.Sizeof(b)) // 8 fmt.Println(unsafe.Sizeof(e)) // 0 }
출력에서 볼 수 있듯이 빈 구조체의 메모리 크기는 0입니다.
동일한 주소
아무리 많은 빈 구조체를 생성하더라도 모두 동일한 주소를 가리킵니다.
package main import ( "fmt" ) func main() { var e struct{} var e2 struct{} fmt.Printf("%p\n", &e) // 0x90b418 fmt.Printf("%p\n", &e2) // 0x90b418 fmt.Println(&e == &e2) // true }
상태 없음
빈 구조체는 필드를 포함하지 않으므로 상태를 유지할 수 없습니다. 이는 상태가 없는 객체 또는 조건을 나타내는 데 매우 유용하게 만듭니다.
왜 제로 메모리 및 동일한 주소인가?
빈 구조체가 왜 크기가 0이고 동일한 주소를 공유하는지 이해하려면 Go의 소스 코드를 자세히 살펴봐야 합니다.
/go/src/runtime/malloc.go
// base address for all 0-byte allocations var zerobase uintptr func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer { // ... if size == 0 { return unsafe.Pointer(&zerobase) } // ...
malloc.go
의 이 발췌문에 따르면 할당할 객체의 크기가 0이면 zerobase
에 대한 포인터를 반환합니다. zerobase
는 0바이트 객체를 할당하는 데 사용되는 기본 주소이며 실제 메모리 공간을 차지하지 않습니다.
빈 구조체의 사용 시나리오
빈 구조체는 주로 다음 세 가지 시나리오에서 사용됩니다.
- Set 데이터 구조 구현
- 채널에서 신호로 사용
- 메서드 수신자로 사용
Set 데이터 구조 구현
Go에는 기본 제공 Set 유형이 없지만 map 유형을 사용하여 구현할 수 있습니다. map 키는 고유하므로 요소를 키로 저장하고, 값은 중요하지 않으므로 메모리를 절약하기 위해 빈 구조체를 값으로 사용합니다.
package main import "fmt" type Set[K comparable] map[K]struct{} func (s Set[K]) Add(val K) { s[val] = struct{}{} } func (s Set[K]) Remove(val K) { delete(s, val) } func (s Set[K]) Contains(val K) bool { _, ok := s[val] return ok } func main() { set := Set[string]{} set.Add("Leapcell") fmt.Println(set.Contains("Leapcell")) // true set.Remove("Leapcell") fmt.Println(set.Contains("Leapcell")) // false }
채널 신호로 사용
빈 구조체는 종종 Goroutine 간의 신호 처리에 사용됩니다. 특히 채널에서 전달되는 실제 데이터에는 관심이 없고 신호에만 관심이 있는 경우에 그렇습니다. 예를 들어 빈 구조체 채널을 사용하여 Goroutine에 작업 중지 알림을 보낼 수 있습니다.
package main import ( "fmt" "time" ) func main() { quit := make(chan struct{}) go func() { // 작업 시뮬레이션 fmt.Println("Working...") time.Sleep(3 * time.Second) // 종료 신호 보내기 close(quit) }() // 종료 신호가 닫힐 때까지 차단하고 대기합니다. <-quit fmt.Println("종료 신호가 수신되어 종료합니다...") }
이 예제에서는 quit
채널이 생성되고 별도의 Goroutine이 일부 작업을 시뮬레이션합니다. 작업을 완료한 후 quit
채널을 닫아 종료 신호를 보냅니다. 기본 함수는 신호를 받을 때까지 <-quit
에서 차단된 다음 메시지를 출력하고 종료합니다.
채널은 빈 구조체 유형을 사용하므로 추가 메모리 오버헤드가 발생하지 않습니다.
Go의 표준 라이브러리에서 context
패키지의 Context
인터페이스에는 작업 완료 상태를 알리는 데 사용되는 채널을 반환하는 Done()
메서드가 있습니다. 이 반환된 채널은 빈 구조체를 유형으로 사용합니다.
type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key any) any }
메서드 수신자로 사용
인터페이스를 구현하기 위해 메서드 그룹을 정의해야 하지만 구현에 데이터를 저장할 필요가 없는 경우가 있습니다. 이러한 경우 빈 구조체를 사용할 수 있습니다.
type Person interface { SayHello() Sleep() } type LPC struct{} func (c LPC) SayHello() { fmt.Println("[Leapcell] Hello") } func (c LPC) Sleep() { fmt.Println("[Leapcell] Sleeping...") }
이 예제에서는 인터페이스 Person
과 구조체 LPC
를 정의하고 메서드 SayHello
및 Sleep
으로 Person
인터페이스를 구현합니다.
LPC
는 빈 구조체이므로 메모리 오버헤드를 추가하지 않습니다.
요약
이 글에서는 먼저 Go 언어에서 빈 구조체의 개념과 정의를 소개했습니다. 빈 구조체는 두 가지 방식으로 정의할 수 있습니다.
다음으로 빈 구조체의 특징을 살펴보았습니다. 여기에는 제로 메모리 사용량과 이 유형의 여러 변수가 동일한 주소를 공유한다는 사실이 포함됩니다.
다음으로 소스 코드를 더 자세히 살펴보고 Go에서 빈 구조체가 제로 메모리와 동일한 주소를 갖는 이유를 조사했습니다. 그 이유는 할당할 객체의 크기(size
)가 0이면 Go가 zerobase
에 대한 포인터를 반환하기 때문입니다.
마지막으로 빈 구조체의 세 가지 사용 사례를 나열하고 다음과 같은 몇 가지 일반적인 실제 시나리오를 코드 예제와 함께 보여주었습니다.
map[K]struct{}
을 사용하여 Set 구현- 채널에서 신호 처리를 위해 빈 구조체 사용
- 데이터 저장소가 필요하지 않은 경우 수신자로 빈 구조체 사용
Go 프로젝트 호스팅을 위한 최고의 선택, Leapcell입니다.
Leapcell은 웹 호스팅, 비동기 작업 및 Redis를 위한 차세대 서버리스 플랫폼입니다.
다국어 지원
- Node.js, Python, Go 또는 Rust로 개발하십시오.
무료로 무제한 프로젝트 배포
- 사용량에 대해서만 지불하십시오. 요청도 없고 요금도 없습니다.
탁월한 비용 효율성
- 유휴 요금 없이 사용한 만큼만 지불하십시오.
- 예: $25는 평균 응답 시간 60ms에서 694만 건의 요청을 지원합니다.
간소화된 개발자 경험
- 간편한 설정을 위한 직관적인 UI.
- 완전 자동화된 CI/CD 파이프라인 및 GitOps 통합.
- 실행 가능한 통찰력을 위한 실시간 메트릭 및 로깅.
손쉬운 확장성 및 고성능
- 고도의 동시성을 쉽게 처리하기 위한 자동 확장.
- 운영 오버헤드가 전혀 없습니다. 구축에만 집중하십시오.
설명서에서 자세히 알아보십시오!
X에서 팔로우하세요: @LeapcellHQ