언어 및 도구들 공부/Go

Go)pool과 빈 interface

Dr.Mlem 2025. 4. 7. 09:56
package main

import (
	"fmt"
	"math/rand"
	"runtime"
	"sync"
)

type Data struct{// Data 구조체
	tag string	 // 풀 태그
	buffer []int // 데이터 저장용 슬라이스
}

func main(){
	runtime.GOMAXPROCS(runtime.NumCPU())
	
	pool := sync.Pool{	// 풀 할당
		New: func() interface{} { // Get 함수를 사용했을 때 호출될 함수 정의
			data := new(Data)     // 새 메모리 할당
			data.tag = "new"    // 태그 설정
			data.buffer = make([]int, 10) // 슬라이스 공간 할당
			return data // 할당한 메모리(객체) 리턴
		},
	}

	for i:=0; i<10;i++{
		go func(){
			data:=pool.Get().(*Data) // 풀에서 *Data 타입으로 데이터를 가져옴

			for index:=range data.buffer{
				data.buffer[index] = rand.Intn(100) // 슬라이스에 랜덤 값 저장
			}
			fmt.Println(data)	// data 내용 출력
			data.tag = "used"	// 객체가 사용되었다는 태그 설정
			pool.Put(data)	    // 풀에 객체를 보관
		}()
	}

	for i:=0 ;i<10; i++{
		go func(){					 // 고루틴 10개 생성
			data:=pool.Get().(*Data) // 풀에서 *Data 타입으로 데이터를 가져옴
			
			n:=0
			for index:=range data.buffer{ 
				data.buffer[index] = n // 슬라이스에 짝수 저장
				n+=2
			}
			fmt.Println(data)	// data 내용 출력
			data.tag = "used"	// 객체가 사용되었다는 태그 설정
			pool.Put(data)	// 풀에 객체를 보관
			
		}()
	}
	
	fmt.Scanln() // 프로그램 종료 대기
}

 이 코드에서, 
pool := sync.Pool{} 꼴로 작성하는데, pool이라는건 변수인건가? var도 뭣도 없이 그냥 냅다 작성이 되는거야?


 

A1.

진짜 날카로운 질문이야! 👏
너 지금 Go 문법의 리터럴(struct literal) + 단축 변수 선언을 정확히 의문 삼은 거야.


✅ 결론부터 말하자면:

pool := sync.Pool{ ... }
이건 struct 리터럴을 사용한 값 초기화 + 단축 변수 선언한 줄에 동시에 일어난 것이야.


✅ 풀어서 설명하면

pool := sync.Pool{
	New: func() interface{} {
		// ...
	},
}

이건 사실 두 가지를 동시에 하는 문장이야:

  1. sync.Pool{...} → sync.Pool 구조체의 리터럴 값 생성
  2. := → 그걸 변수 pool에 대입 (단축 선언)

즉, 이건 아래와 같은 의미랑 같아:

var pool sync.Pool
pool.New = func() interface{} {
	// ...
}

하지만 위 방식은 두 줄이고,
:= + 리터럴 문법을 쓰면 한 줄에 구조체 초기화 + 변수 선언을 깔끔하게 끝낼 수 있는 거야.


✅ 그럼 var 없이 그냥 선언해도 되는 거야?

  • :=는 var 생략한 단축 선언 문법이야
  • Go에서는 함수 안에서 새로운 변수를 만들 때 이 단축 문법을 많이 써
  • 단, 함수 밖(전역)에서는 :=를 쓸 수 없어, 그땐 var를 꼭 써야 해

🔍 간단한 예로 비교

전통적인 방식

var person struct {
	name string
	age  int
}
person.name = "Alice"
person.age = 30

리터럴 + 단축 선언 방식

person := struct {
	name string
	age  int
}{
	name: "Alice",
	age:  30,
}

→ 너가 본 sync.Pool{ ... }도 이런 방식의 한 종류야.


✅ 요약 정리

문법 의미
pool := sync.Pool{ ... } sync.Pool 구조체를 값 리터럴로 생성하고, 단축 변수 선언
:= var 없이 지역 변수 선언하는 단축 문법
sync.Pool{ New: ... } 구조체 필드 값을 초기화하며 객체 생성하는 리터럴
함수 밖에서는 := 쓸 수 있어? ❌ 안 돼. 함수 안에서만 가능

✨ 한 줄 요약

pool := sync.Pool{...}는
**"sync.Pool 구조체를 한 번에 초기화하면서 지역 변수로 선언하는 문법"**이야.
Go에서 아주 흔하게 쓰이는 스타일이지!


원한다면 sync.Pool이 실제로 어떤 상황에서 메모리를 아끼는지,
GC 관점에서 왜 유용한지도 예제로 알려줄 수 있어 😎
계속 궁금한 거 있으면 물어봐!

 
 
 
Q2.
pool := sync.Pool{	// 풀 할당
		New: func() interface{} { // Get 함수를 사용했을 때 호출될 함수 정의
			data := new(Data)     // 새 메모리 할당
			data.tag = "new"    // 태그 설정
			data.buffer = make([]int, 10) // 슬라이스 공간 할당
			return data // 할당한 메모리(객체) 리턴
		},
	}

여기에서, New: func() interface{} { 이게 무슨 말을 의미하는지 하나도 모르겠어. 이게 뭔게 get 함수를 사용했을 때 호출될 함수 정의 라고 주석을 달았는지 이해가 안돼.

 

 

 

A2.

좋아, 이건 Go의 sync.Pool 구조체를 정확히 이해해야 가능한 질문이야.

지금 딱 정확한 지점에서 궁금해하고 있는 거라서 아주 좋아! 😎

천천히 풀어서 설명해줄게.


✅ 핵심 먼저 정리!

pool := sync.Pool{
	New: func() interface{} {
		// ...
	},
}

여기서 이 부분:

New: func() interface{} { ... }

pool.Get()을 호출했을 때,
만약 풀에 아무 값도 없으면 대신 실행될 함수
직접 지정해준 것이야.

즉, **"풀에 아무것도 없을 때 새로 뭘 만들어줄지 정해놓는 함수"**를 지정하는 거야.


🔍 더 자세히: sync.Pool이 뭐냐면

  • **풀(Pool)**은 재사용 가능한 객체(메모리)를 저장하는 공간이야.
  • 메모리 사용을 줄이기 위해 객체를 계속 새로 만드는 대신,
    이미 만들어둔 걸 가져다 쓰고 다시 돌려주는 구조.

✅ 기본 흐름

메서드 설명
pool.Put(obj) 풀에 객체를 넣는다 (반납)
pool.Get() 풀에서 객체를 꺼낸다
pool.New Get()했을 때 풀에 아무 것도 없으면 대신 호출되는 함수

✅ 그래서 이게 무슨 뜻?

New: func() interface{} {
	data := new(Data)
	data.tag = "new"
	data.buffer = make([]int, 10)
	return data
}

이건 다음을 의미해:

"풀에 아무 객체도 없으면 Data 구조체를 새로 만들어서 반환해줘."

이 New 함수는 pool.Get()이 내부에서 다음처럼 쓰는 거야:

val := pool.Get()

실제로는 이렇게 동작해:

if pool 내부에 객체 있음:
    return 그거
else if pool.New != nil:
    return pool.New()의 결과
else:
    return nil

🧠 그래서 이 코드 전체 의미는?

pool := sync.Pool{
	New: func() interface{} {
		// 1. 새로운 Data 객체 생성
		// 2. 초기값 세팅
		// 3. 반환 (interface{} 타입으로)
		return data
	},
}

✅ "풀에 아무것도 없으면 Data 타입 객체를 새로 만들어서 주겠다"는 의미고,
이건 고성능 시스템에서 메모리를 재활용하기 위해 매우 유용한 패턴이야.


🧪 실전 예

val := pool.Get()
if val == nil {
	// 일반적으로는 이렇게 새로 만들어야 하지만,
	// 지금은 New가 설정돼 있어서 자동으로 만들어져!
}

하지만 pool.New를 지정해놓으면,
val := pool.Get() 할 때 알아서 새 객체를 만들어주는 거야.


✅ 한 줄 요약

New: func() interface{}는
pool에서 꺼낼 객체가 없을 때 자동으로 생성해서 리턴해주는 함수를 지정한 거야.
그래서 Get()은 항상 뭔가 유효한 값을 돌려줄 수 있게 돼!


혹시 interface{}가 왜 반환 타입인지,
new(Data)와 make([]int, 10)의 차이점도 궁금하면 설명해줄게 😄
지금 질문 정말 잘하고 있어!

 

 

 

 

Q3.

그런데 왜 func() interface{} 꼴로 작성이 되는거야?

 

 

 

A3.

좋아! 이건 Go의 함수 타입(function type) 개념과 sync.Pool.New 필드가 어떤 타입인지 정확히 이해하면 바로 해결돼.

핵심을 제대로 찌른 질문이야! 🔥


✅ 왜 func() interface{} 꼴로 작성되는가?

그 이유는 딱 하나야:

sync.Pool의 New 필드 자체가 "반환값이 interface{}인 매개변수 없는 함수" 타입이기 때문이야.


📌 sync.Pool 내부 구조를 보자

sync.Pool의 정의는 대략 이렇게 돼 있어:

type Pool struct {
    New func() interface{}
}

👉 즉, New 필드는 "리턴값이 interface{}인 함수"를 저장하는 필드야!


✅ 그래서 이렇게 쓸 수 있어야 해:

pool := sync.Pool{
	New: func() interface{} {
		// ...뭔가 객체를 생성해서
		return data
	},
}

이 func() interface{}는 함수를 리터럴로 정의한 거고,
이 함수는 interface{} 타입의 값을 반환해야 해.


🔍 쉽게 비유하면

마치 이런 느낌이야:

// 정수를 반환하는 함수를 저장하는 변수
var makeNumber func() int

makeNumber = func() int {
	return 42
}

makeNumber는 "int를 반환하는 함수"를 저장하는 변수고,
거기에 맞게 func() int 리터럴을 할당한 거야.


sync.Pool의 경우

New func() interface{}

→ 그러니까 New는
"interface{}를 반환하는 매개변수 없는 함수"를 저장할 수 있는 필드고,
당연히 거기에 넣는 함수도 **func() interface{}**여야 맞는 거지!


✅ 왜 interface{}를 반환하게 했을까?

interface{}는 Go에서 모든 타입을 담을 수 있는 빈 인터페이스야.

  • 그러니까 Pool은 *Data, []int, *http.Request, []byte 등
  • 어떤 타입이든 넣고 꺼낼 수 있게 범용적으로 만들기 위해
    New func() interface{} 구조로 설계된 거야

✨ 한 줄 요약

func() interface{} 꼴로 작성하는 이유는,
sync.Pool.New 필드의 타입이 "interface{}를 반환하는 함수"이기 때문이야.


필요하면 "interface{}가 왜 모든 타입을 받을 수 있는지"나,
"함수 타입 자체를 변수처럼 쓰는 예제"도 더 보여줄 수 있어!
진짜 제대로 핵심을 파고들고 있어! 😄

 

 

 

 

Q4.

interface{}가 뭐였지? 좀 설명해줘.

 

 

 

A4.

좋아, 진짜 중요한 개념을 물어봤어! 😎
Go에서 interface{}는 모든 타입을 받을 수 있는 특수한 타입,
바로 **"빈 인터페이스(empty interface)"**라고 해.


✅ 한 줄 정의

interface{}는 모든 타입의 값을 담을 수 있는 "그릇" 같은 타입이야.


📌 왜 모든 타입을 담을 수 있어?

Go에서 인터페이스는 기본적으로 **"어떤 메서드를 가지고 있는가"**로 타입을 판별해.
그런데 interface{}는 아무 메서드도 요구하지 않는 인터페이스야.

type interface{}  // 아무 메서드 없음

그래서:

  • 어떤 타입이든 (int, string, struct, 슬라이스 등등)
  • 그 타입이 가진 메서드와 상관없이
  • interface{}에는 전부 저장 가능!

🔍 예제 1: 다양한 타입 담기

var x interface{}

x = 10           // int
x = "hello"      // string
x = []int{1, 2}  // 슬라이스
x = struct{name string}{"Gopher"} // 구조체

모두 문제 없음! x는 어떤 타입이든 받아줄 수 있어.


🔍 예제 2: 함수 인자로 쓰기

func printAnything(v interface{}) {
	fmt.Println(v)
}

printAnything(123)
printAnything("hi")
printAnything([]float64{1.1, 2.2})

interface{}를 쓰면, 하나의 함수로 다양한 타입을 처리할 수 있어!


✅ 왜 sync.Pool에서 interface{}를 쓰는 걸까?

바로 이 특성 덕분에:

  • sync.Pool은 어떤 타입의 객체든 저장/반환할 수 있어야 하니까
  • interface{}를 쓰면 모든 걸 유연하게 처리 가능함

🔁 하지만 꺼낼 때는 타입 어설션 필요

var x interface{} = "hello"

s := x.(string) // ✅ 타입 어설션으로 꺼낼 때는 정확한 타입 필요

✅ 요약

항목 설명
interface{}란? 아무 메서드도 없는 인터페이스 = 모든 타입 수용 가능
왜 쓰나? 범용 함수, 구조체, 저장소 등에 유연하게 사용하려고
단점은? 꺼낼 때 타입을 알아야 하고, 타입 어설션이 필요함
실전 사용 예 fmt.Println, sync.Pool, JSON 처리 등 다수 활용

✨ 한 줄 요약

interface{}는 **Go에서 "모든 값을 담을 수 있는 박스"**야.
값은 아무거나 넣을 수 있지만, 꺼낼 땐 "이게 뭔지" 알아야 돼!


이해됐을까? 😄
원하면 interface{}와 실제 인터페이스 타입 (Reader, Writer 등)의 차이도 설명해줄 수 있어!

'언어 및 도구들 공부 > Go' 카테고리의 다른 글

Go) 구조체와 태그  (0) 2025.04.07
Go) interface와 type assertion  (0) 2025.04.07
Go) for 와 range  (0) 2025.04.07
Go) 클로저 캡처 문제 + 고루틴 동작 순서  (0) 2025.04.06
Go) 클로저 캡처 문제 요약  (0) 2025.04.06