- 책 또는 웹사이트의 내용을 복제하여 다른 곳에 게시하는 것을 금지합니다.
- 책 또는 웹사이트의 내용을 발췌, 요약하여 강의 자료, 발표 자료, 블로그 포스팅 등으로 만드는 것을 금지합니다.
동기화 객체 사용하기
이재홍 http://www.pyrasis.com 2014.12.17 ~ 2015.02.07
풀 사용하기
풀은 객체(메모리)를 사용한 후 보관해두었다가 다시 사용하게 해주는 기능입니다. 객체를 반복해서 할당하면 메모리 사용량도 늘어나고, 메모리를 해제해야 하는 가비지 컬렉터에게도 부담이 됩니다. 즉, 풀은 일종의 캐시라고 할 수 있으며 메모리 할당과 해제 횟수를 줄여 성능을 높이고자 할 때 사용합니다. 그리고 풀은 여러 고루틴에서 동시에 사용할 수 있습니다.
sync 패키지에서 제공하는 풀의 구조체와 함수는 다음과 같습니다.
- sync.Pool
- func (p *Pool) Get() interface{}: 풀에 보관된 객체를 가져옴
- func (p *Pool) Put(x interface{}): 풀에 객체를 보관
풀을 사용하여 정수 10개짜리 슬라이스를 공유해보겠습니다. 첫 번째 고루틴 그룹에서는 슬라이스에 랜덤한 숫자를 10개를 저장한 뒤 출력하고, 두 번째 고루틴 그룹에서는 짝수 10개를 저장한 뒤 출력합니다.
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([]int, 10) // 슬라이스 공간 할당
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 필드에 초기화 함수를 만들어줍니다.
pool := sync.Pool{ // 풀 할당
New: func() interface{} { // Get 함수를 사용했을 때 호출될 함수 정의
data := new(Data) // 새 메모리 할당
data.tag = "new" // 태그 설정
data.buffer = make([]int, 10) // 슬라이스 공간 할당
return data // 할당한 메모리(객체) 리턴
},
}
New 필드에 정의된 함수는 Get 함수를 사용했을 때 호출됩니다. 단, 풀에 객체가 없을 때만 호출되므로 객체를 생성하고, 메모리를 할당하는 코드를 작성합니다. 풀에 객체가 들어있다면 New 필드의 함수는 호출되지 않고, 보관된 객체가 리턴됩니다. 여기서는 객체를 새로 할당했다는 의미에서 tag 필드에 new를 대입합니다(tag 필드는 풀의 객체 사용 상황을 알아보기 위한 예제이며 필수 요소는 아닙니다).
풀에서 Get 함수로 객체를 꺼낸 뒤에는 반드시 Type assertion을 해주어야 합니다. 여기서는 New 필드의 함수에서 new(Data)로 메모리를 할당했으므로 포인터 형인 (*Data)로 변환합니다.
data := pool.Get().(*Data) // 풀에서 *Data 타입으로 데이터를 가져옴
이제 슬라이스에 적절히 데이터를 채워넣은 뒤 화면에 출력합니다. 그리고 객체 사용이 끝났다는 의미에서 tag 필드에 used를 대입합니다. 객체를 사용이 끝났으므로 다시 Put 함수를 사용하여 객체를 풀에 보관합니다.
for index := range data.buffer {
data.buffer[index] = rand.Intn(100) // 슬라이스에 랜덤 값 저장
}
fmt.Println(data) // data 내용 출력
data.tag = "used" // 객체가 사용되었다는 태그 설정
pool.Put(data)
출력 결과를 보면 객체가 새로 할당되는 횟수는 얼마 안되고 대부분 풀에 보관된 객체를 사용하는 것을 알 수 있습니다.
&{new [81 87 47 59 81 18 25 40 56 0]} ← 객체가 새로 할당된 경우
&{used [0 2 4 6 8 10 12 14 16 18]}
&{used [41 8 87 31 29 56 37 31 85 26]}
&{new [94 11 62 89 28 74 11 45 37 6]}
&{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 [0 2 4 6 8 10 12 14 16 18]}
&{used [23 53 37 33 41 59 33 43 91 2]}
&{used [0 2 4 6 8 10 12 14 16 18]}
&{used [0 2 4 6 8 10 12 14 16 18]}
&{used [0 2 4 6 8 10 12 14 16 18]}
&{used [0 2 4 6 8 10 12 14 16 18]}
&{used [0 2 4 6 8 10 12 14 16 18]}
&{used [0 2 4 6 8 10 12 14 16 18]}
&{used [0 2 4 6 8 10 12 14 16 18]}
&{used [0 2 4 6 8 10 12 14 16 18]}
&{new [95 66 28 58 47 47 87 88 90 15]}
&{new [78 36 46 7 40 3 52 43 5 98]}
이처럼 풀을 사용하면 메모리를 효율적으로 관리할 수 있습니다. 단, 수명 주기가 짧은 객체는 풀에 적합하지 않습니다.
저작권 안내
이 웹사이트에 게시된 모든 글의 무단 복제 및 도용을 금지합니다.- 블로그, 게시판 등에 퍼가는 것을 금지합니다.
- 비공개 포스트에 퍼가는 것을 금지합니다.
- 글 내용, 그림을 발췌 및 요약하는 것을 금지합니다.
- 링크 및 SNS 공유는 허용합니다.