이번 게시글에서는 고루틴 사용시 주의점에 대해서 정리합니다.
억지 예제를 만든 느낌이만.. 고루틴이 무한정 증가하는걸 막고싶은경우 활용할만한 예제입니다.
1. 예제
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
doSomething()
}
func doSomething() {
for {
data := getDataFromServer()
analyzeAndSaveData(data)
time.Sleep(time.Millisecond * 1000)
}
}
// server는 난수 x개를 생성해서 내려줍니다.
func getDataFromServer() []int {
var data []int
loopCnt := rand.Intn(300)
for i := 0; i < loopCnt; i++ {
data = append(data, rand.Intn(10))
}
return data
}
// 난수들의 결과를 더한후 저장합니다.
func analyzeAndSaveData(data []int) {
res := 0
for _, item := range data {
res += item
time.Sleep(time.Millisecond * 10)
}
saveData(res)
}
func saveData(res int) {
// 저장대신 콘솔출력으로 대체합니다.
fmt.Println(res)
}
타 서버에서 데이터 요청을 폴링하고 해당 데이터를 우리 애플리케이션에서 분석하고 처리하는 로직입니다.
해당로직은 1초의 슬립을 걸어 1초를 주기로 실행되도록 만들었습니다.
해당 애플리케이션을 실행해보면 타 서버에서 데이터를 내려주는 시간이 일정하더라도 우리 애플리케이션의 분석시간이 일정하지 않아서 예상보다 훨씬 오랜 시간이 소요됩니다.
1초마다 데이터를 요청해서 분석, 저장하는게 목표였으나 기존의 목표와는 다른 결과가 나오게 됩니다.
분석 저장을 고루틴으로 변경해봅니다.
2. 분석 저장을 고루틴으로!
func doSomething() {
for {
data := getDataFromServer()
go analizeAndSaveData(data)
time.Sleep(time.Microsecond * 1000)
}
}
이제 분석저장 로직을 고루틴으로 진행시키므로
서버에 1초마다 요청을 보낼수 있게 됐습니다.
3. 문제점
해당 애플리케이션은 메모리가 폭발할 수 있는 애플리케이션입니다.
analizeAndSaveData() 메서드에 양이 많은 데이터가 계속 들어와 소요시간이 늘어난다면 고루틴은 무한대로 쌓일 수 있고, 애플리케이션은 위험에 빠질 수 있습니다.(폴링 등의 조건이 아니더라도 외부 조건에 의해 고루틴을 생성된다면 고루틴이 무한정 쌓일수 있는 환경은 아닌지 고려해야합니다)
4. 폴링시에 버퍼채널을 활용하여 해결하기
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
doSomething()
}
func doSomething() {
consumerCnt := 5
// 고루틴이 처리해야 될 정보를 채널로 만듭니다
// 일반채널로 만들면 컨슈머가 없을때 프로듀서가 블락됩니다.
// 버퍼채널은 버퍼채널이 가득찰때까지 프로듀서가 블락되지 않습니다.
queue := make(chan []int, 10000)
for i := 0; i < consumerCnt; i++ {
go runConsumer(queue)
}
runProducer(queue)
}
func runProducer(queue chan []int) {
for {
data := getDataFromServer()
// 고루틴이 처리해야 될 정보를 채널에 넣어줍니다.
queue <- data
time.Sleep(time.Millisecond * 1000)
}
}
func runConsumer(queue chan []int) {
for {
data := <-queue
analyzeAndSaveData(data)
}
}
// server는 난수 x개를 생성해서 내려줍니다.
func getDataFromServer() []int {
var data []int
loopCnt := rand.Intn(300)
for i := 0; i < loopCnt; i++ {
data = append(data, rand.Intn(10))
}
return data
}
// 난수들의 결과를 더한후 저장합니다.
func analyzeAndSaveData(data []int) {
res := 0
for _, item := range data {
res += item
time.Sleep(time.Millisecond * 10)
}
saveData(res)
}
func saveData(res int) {
// 저장대신 콘솔출력으로 대체합니다.
fmt.Println(res)
}
1초에 한번씩 고루틴(프로듀서)으로 데이터를 요청한 후 데이터를 채널에 넣어줍니다.
여러개의 고루틴(컨슈머)을 만들어 해당 큐의 데이터를 꺼내서 분석, 저장하도록 합니다.
루틴풀, 고루틴 큐처럼 사용하여 컨슈머의 갯수를 지정해서 고루틴이 무한정 증가하는걸 방어합니다.
버퍼채널을 사용할경우 버퍼채널이 가득차면 버퍼채널 송신이 블락되어 프로듀서의 로직이 진행되지 않으므로 컨슈머를 넉넉하게 만들어놓거나 필요시 컨슈머를 스케일링하여 사용하도록 합니다.
'golang' 카테고리의 다른 글
[golang] go언어 mockup을 통한 테스트 코드 작성법(go mock) (0) | 2022.04.11 |
---|---|
[golang] aws-sdk-go-v2 의 s3 copyobject 사용법(copyobject example) (0) | 2022.04.11 |
[golang] golang 포인터 예제 (0) | 2022.03.02 |
[golang] decorate 패턴을 사용하여 timer 구축하기(golang 시간 차이 구하기) (0) | 2022.02.28 |
[golang] vue와 golang echo framework 연동하기 (0) | 2022.01.24 |
댓글