개요 (Overview)
Golang에서는 고루틴이라는 비동기 작업을 생성할 수 있고 쓰레드와 비교해 가볍기 때문에 동시에 수백 개를 만들어도 메모리를 많이 사용하지 않는다. 다만 고루틴은 비동기로 실행되고 쓰레드와 다르게 종료할 수 있는 함수를 제공하지 않는다. 즉 고루틴을 생성한 함수가 종료되더라도 생성된 고루틴은 남아서 계속 실행된다. 따라서 별도의 시그널을 통해서 종료해줘야 한다.
잠깐 실행되는 애플리케이션이면 큰 문제가 없지만 서버 애플리케이션과 같이 한번 실행하면 짧게는 며칠 길게는 몇 달을 넘게 계속 돌린다. 이때 생성한 고루틴을 제때 정리해주지 않으면 실행하는 고루틴이 계속 쌓여서 Memory Leak이 발생하게 되고 아무리 가벼운 고루틴이더라도 계속 쌓이면 결국 OOM(Out of Memory)가 발생할 수 있다. 서버의 가용성에 문제가 발생할 수 있고 잘못되면 서비스가 일시적으로 중단될 가능성이 있다.
따라서 생성한 고루틴이 더 이상 사용할 필요가 없어지면 꼭 정리를 해줘야 하는데 이때 사용할 수 있는 방법은 여러 가지가 있지만 여기서는 Golang에서 제공하는 Context와 Channel을 이용해보려고 한다.
컨텍스트 (Context)
단순 번역으로는 `맥락`을 의미한다.
Golang에서 컨텍스트는 작업을 수행할 때 데이터를 전달하고 조건에 따라 흐름을 제어하는 역할을 해주는 패키지이다.
기본적으로 생성하는 컨텍스트는 Background, TODO가 있으며 특정 데이터를 전달할 때는 WithValue를 이용해서 컨텍스트를 생성할 수 있다. 이렇게 생성된 컨텍스트는 절대(never) 취소되지 않으며 데이터도 없고 만료 일시도 없다.
컨텍스트 생성
- context.Background()
- context.TODO()
- context.WithValue(parent Context, key, val any) Context
- 인자로 컨텍스트를 요구하기 때문에 위 Background, TODO 중 하나를 만들어서 인자로 넣어주면 된다.
컨텍스트 흐름 제어 추가
- context.WithCancel(parent Context) Context
- context.WithCancelCause(parent Context) Context
- context.WithDeadline(parent Context, d time.Time) Context
- context.WithDeadlineCause(parent Context, d time.Time, cause error) Context
- context.WithTimeout(parent Context, timeout time.Duration) Context
- context.WithTimeoutCause(parent Context, timeout time.Duration, cause error) Context
Canel은 취소 기능을 제공하여 필요할 때 취소하여 흐름을 제어할 수 있다.
Deadline과 Timeout은 컨텍스트에 대한 만료 시간을 제공하여 처리 시간에 대해 흐름 제어가 가능하며 Deadline은 지정된 일시(time.Duration)를 인자로 받으며 Timeout은 기간(time.Duration)을 인자로 받아 내부에서 다시 WithDeadline을 호출한다. 즉 WithDeadline은 개발자가 지정된 일시를 계산하지 않고 특정 기간만 지정할 수 있는 편의성을 제공하는 함수다.
Cause는 취소될 때 error 타입의 인자를 같이 받아서 왜 취소되었는지 알 수 있도록 제공하는 함수다.
WithCancelCause 함수의 경우 컨텍스트를 생성할 때는 error 인자를 받지 않고 취소할 때 error 인자를 받아서 취소 사유를 알 수 있으며 그 외에는 생성 시 인자를 받아서 사유를 알 수 있도록 되어 있다.
사용 방법 (How to use)
함수 처리 중 특정 사유(인터럽트, 에러 등)로 인한 고루틴 흐름 제어 (컨텍스트 취소)
- context.Background() 또는 TODO()로 빈 컨텍스트 생성
- 생성된 빈 컨텍스트를 이용해 WithCancel()로 취소 가능한 컨텍스트를 생성
- 생성된 컨텍스트를 포함하여 고루틴 생성
- 작업 (메인 함수는 고루틴 완료 대기)
- 인터럽트 또는 메인 함수에서 에러가 발생하여 취소(Cancel) 함수 호출
- 취소 함수 호출 후 실행 중인 고루틴이 모두 종료될 때까지 대기
- 모두 종료 후 메인 함수 정리 후 종료
샘플 코드 (Sample code)
별도의 기능을 사용하지 않으면 생성한 고루틴의 상태를 알 수 없기 때문에 sync.WaitGroup을 이용해 생성한 고루틴의 상태를 간접적으로 알 수 있도록 하였다.
package main
import (
"context"
"log"
"sync"
"time"
)
// user defined asynchronous task
func async_task(wg *sync.WaitGroup, ctx context.Context, i int) {
for {
select {
case <-ctx.Done():
// using break does not escape for-loop
// should use return instead or flag
log.Printf("terminate async task %d", i)
wg.Done()
return
default:
log.Printf("async task %d is running", i)
time.Sleep(time.Duration(i) * time.Second)
}
}
}
func main() {
log.Printf("start basic-goroutine-manage")
waitGroup := sync.WaitGroup{}
ctx, cancelFn := context.WithCancel(context.Background())
// run goroutines
for i := 1; i <= 3; i++ {
waitGroup.Add(1)
go async_task(&waitGroup, ctx, i)
}
// cancel goroutines after 10 seconds
go func() {
log.Printf("wait for 10 seconds")
time.Sleep(time.Duration(10) * time.Second)
log.Printf("time is over, cancel goroutines")
cancelFn()
}()
// wait goroutines
log.Printf("wait goroutines")
waitGroup.Wait()
log.Printf("all goroutines are terminated")
log.Printf("terminate basic-goroutine-manage")
}
'Developer > Go' 카테고리의 다른 글
Golang 고루틴(goroutine) 라이프사이클 관리 - channel (0) | 2024.04.24 |
---|---|
컨테이너 환경에서 GOMAXPROCS 이슈 (0) | 2024.01.17 |
Golang defer 실행 순서 (1) | 2024.01.07 |