Notice
Recent Posts
Recent Comments
Link
«   2025/03   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
Archives
Today
Total
관리 메뉴

Code Habit

Go) sync.pool 본문

카테고리 없음

Go) sync.pool

코드베어 2020. 6. 11. 08:58

풀은 객체(메모리)를 사용한 후 보관해두었다가 다시 사용하게 해주는 기능이다. 객체를 반복해서 할당하면 메모리 사용량도 늘어나고, 메모리를 해제해야 하는 가비지 컬렉터에게도 부담이 된다. 즉 풀은 일종의 캐시라고 할 수 있으며 메모리 할당과 해제 횟수를 줄여 성능을 높이고자 할 때 사용한다. 그리고 풀은 여러 고루틴에서 동시에 사용할 수 있다.

 

Sync 패키지에서 제공하는 풀의 구조체와 함수는 다음과 같다.

  • sync.Pool
  • func (p *Pool) Get() interface{} : 풀에 보관된 객체를 가져옴
  • func (p *Pool) Put(x interface{}) : 풀에 객체를 보관

사용 예제이다.

package main
 
import (
    "fmt"
    "math/rand"
    "runtime"
    "sync"
)
 
type Data struct {    // Data 구조체 정의
    tag string         // 풀 태그
    buffer []int    // 데이터 저장용 슬라이스
}
 
func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())    // 모든 CPU 사용
    
    pool := sync.Pool {                     // 풀 할당
       New: func() interface{} {           // Get 함수를 사용했울 때 호출될 함수 정의
            data := new(Data)               // 새 메모리 할당
            data.tag = "new"                // 태그 설정
            data.buffer = make([]int10)   // 슬라이스 공간 할당
            return data                     // 할당한 메모리(객체) 리턴
        },
    }
 
    for i := 0; i < 10; i++ {
       go func() {                           // 고루틴 10개 생성
            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()
}

풀은 sync.Pool을 할당한 뒤에 Get, Put 함수로 사용한다. 먼저 sync.Pool을 할당한 뒤 New 필드에 초기화 함수를 만들어 준다. New 필드에 정의된 함수는 Get 함수를 사용했을 때 호출된다. 단, 풀에 객체가 없을 때만 호출되므로 객체를 생성하고, 메모리를 할당하는 코드를 작성한다. 풀에 객체가 들어 있다면 New 필드의 함수는 호출되지 않고, 보관된 객체가 리턴된다. 여기서는 객체를 새로 할당했다는 의미에서 tag 필드에  new를 대입한다. ( tag 필드는 풀의 객체 사용 상황을 알아보기 위한 예제이며 필수 요소는 아니다. )

 

풀에서 Get 함수로 객체를 꺼낸 뒤에는 반드시 Type assertion을 해주어야 한다. 여기서는 New 필드의 함수에서 New(
Data)로 메모리를 할당했으므로 포인터 형인 (*Data)로 변환한다.

 

객체 사용이 끝났다는 의미에서 tag 필드에 used를 대입하고( 필수는 아니다 ! ) 객체 사용이 끝났으므로 다시 Put 함수를 사용하여 객체를 풀에 보관한다.

 

실행 결과 객체가 새로 할당된느 횟수는 얼마 안 되고 대부분 풀에 보관된 객체를 사용할 것이다.

 

이처럼 풀을 사용하면 메모리를 효율적으로 관리할 수 있다. 단, 수명 주기가 짧은 객체는 풀에 적합하지 않다.

 

실행 결과

&{new [81 87 47 59 81 18 25 40 56 0]}
&{used [94 11 62 89 28 74 11 45 37 6]} 
&{used [95 66 28 58 47 47 87 88 90 15]} 
&{used [41 8 87 31 29 56 37 31 85 26]} 
&{used [13 90 94 63 33 47 78 24 59 53]} 
&{used [57 21 89 99 0 5 88 38 3 55]} 
&{used [51 10 5 56 66 28 61 2 83 46]} 
&{used [63 76 2 18 47 94 77 63 96 20]}
&{used [23 53 37 33 41 59 33 43 91 2]} 
&{used [78 36 46 7 40 3 52 43 5 98]} 
&{used [0 2 4 6 8 10 12 14 16 18]} 
&{used [0 2 4 6 8 10 12 14 16 18]} 
&{new [0 2 4 6 8 10 12 14 16 18]} 
&{used [0 2 4 6 8 10 12 14 16 18]}