Golang에서 객체 재활용을 위한 pool을 제공하는데요. 이 객체 풀에 대한 내용을 정리해 봅니다.
먼저 풀에 넣고 재활용하고자 하는 객체 타입을 아래처럼 정의합니다.
type DB struct {
bConnected bool
}
func (c *DB) connect() {
c.bConnected = true
fmt.Println("[connection completed]")
}
func (c *DB) query() {
if c.bConnected {
fmt.Println(" [query completed]")
} else {
fmt.Println(" [could not query]")
}
}
데이터베이스 객체에 대해 흉내를 낸 DB 타입인데요. DB는 미리 DB 서버에 연결을 한번 맺어 놓고 계속 쿼리를 날립니다. 이 DB 타입은 DB 서버에 연결하는 connect 매서드와 쿼리를 날리는 query 매서드가 있고, DB 서버에 연결이 되어 있는지를 확인할 수 있는 bConnected 필드 변수를 가지고 있는데, 이 필드 변수는 connect 매서드를 호출하면 true로 변경됩니다.
자, 이제 이 DB 객체를 미리 연결해 놓고 풀에서 가져다 재활용하는 코드를 살펴보겠습니다.
func main() {
pool := sync.Pool{
New: func() interface{} {
r := new(DB)
r.bConnected = false
return r
},
}
for i := 0; i < 10; i++ {
go func() {
c := pool.Get().(*DB)
if !c.bConnected {
c.connect()
}
c.query()
pool.Put(c)
}()
}
fmt.Scanln()
}
Key Point가 되는 부분만을 살펴보면, 먼저 Golang의 Pool 객체를 생성하기 위해서 2~8번처럼 New 필드에 재사용할 객체를 생성해주는 함수를 정의해줘야 합니다. 4번처럼 앞서 DB 타입의 객체를 생성하고, 아직 connect 매서드를 호출하지 않았으므로 5번에서 bConnected를 false로 지정합니다. 물론 지정하지 않아도 Null 값으로 false가 저장되어 있지만 명확히 하는 것이 좋으니 이리 했습니다. 그리고 6번에서 생성한 DB 객체를 반환합니다. 이 New 함수는 풀에 객체가 부족할때만 호출되고 재활용 가능한 여분의 객체가 있다면 New 함수 호출 없이 여분의 객체 중 하나를 가져다가 사용합니다.
이제 10번의 for 문에서 10번에 걸쳐 db 객체를 통해 쿼리를 날립니다. go 루틴으로 쿼리를 동시에 10번 날리는 경우인데요. go 루틴의 함수를 보면.. 먼저 12번에서 DB 객체를 풀에서 가져옵니다. 여기서 타입단언(Type Assertion)을 통해 DB 타입이라는 것을 분명히 합니다. 그리고 13번에서 아직 연결이 되어 있지 않다면 connect 함수를 호출하구요. 17번에서 쿼리를 날립니다. DB 객체를 다 사용했다면 18번 코드에서 다시 풀에 반환합니다.
만약 풀을 사용하지 않았다면 10번의 DB 연결과 10번의 DB 쿼리가 발생할 것입니다만.. 우리는 풀을 사용했기 때문에 DB 쿼리는 10번을 했지만 DB 연결은 그보다 적을 것입니다. 제 PC에서는의 실행 결과는 아래와 같습니다.
[connection completed]
[query completed]
[query completed]
[query completed]
[query completed]
[query completed]
[query completed]
[query completed]
[connection completed]
[query completed]
[query completed]
[connection completed]
[query completed]
실행할때마다 그 결과는 매번 달라지기는 했지만, 분명한 것은 DB 쿼리에 대한 query completed 문자열은 항상 10번 표시되고 connection completed는 그보다 적게 표시된다는 것입니다.