빠른 비교
time.Sleep(500 * time.Millisecond)
timer := time.NewTimer(2 * time.Second)
defer timer.Stop()
ticker := time.NewTicker(time.Second)
defer ticker.Stop()Go의 시간 처리는 한 번 기다림, 한 번 알림, 반복 알림, 작업 수명 제한을 나눠서 고릅니다.
갈리는 기준
| 상황 | 선택 |
|---|---|
| 지금 goroutine을 잠깐 멈춤 | time.Sleep |
| 한 번만 나중에 알림 | time.NewTimer |
| 일정 간격 반복 실행 | time.NewTicker |
| 요청이나 작업 전체 시간 제한 | context.WithTimeout |
| 간단한 select timeout | time.After |
time.Sleep은 현재 goroutine을 멈추기만 합니다. 취소 신호를 받을 필요가 있거나 다른 channel과 같이 기다려야 하면 select와 timer/context를 쓰는 편이 낫습니다.
기본 흐름
한 번 기다릴 때는 timer를 쓴다
timer := time.NewTimer(2 * time.Second)
defer timer.Stop()
select {
case <-timer.C:
return nil
case <-ctx.Done():
return ctx.Err()
}Timer는 channel로 완료 신호를 받기 때문에 select와 잘 맞습니다. 취소 가능한 대기 흐름에서는 Sleep보다 timer가 구조를 더 분명하게 만듭니다.
반복 작업은 ticker를 쓴다
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
refresh()
case <-ctx.Done():
return ctx.Err()
}
}Ticker는 멈출 때까지 주기적으로 신호를 보냅니다. 더 이상 쓰지 않을 때는 Stop()으로 내부 리소스를 정리합니다.
타임아웃
작업 수명은 context로 제한한다
ctx, cancel := context.WithTimeout(parent, 2*time.Second)
defer cancel()
if err := callAPI(ctx); err != nil {
return err
}DB, HTTP client, 외부 API 호출처럼 작업 전체에 시간 제한이 필요하면 context.WithTimeout이 기준입니다. timeout은 단순 대기 시간이 아니라 작업의 수명 정책입니다.
time.After는 짧은 select에만 쓴다
select {
case result := <-done:
return result
case <-time.After(2 * time.Second):
return errors.New("timeout")
}time.After는 간단하지만 호출할 때마다 timer를 만듭니다. 루프 안에서 반복 생성되는 구조라면 Timer를 직접 만들고 재사용이나 정리를 검토합니다.
주의할 점
Ticker를 만들고 멈추지 않으면 불필요한 작업이 계속 살아 있을 수 있습니다. 또한 timeout이 필요한 함수에서 time.Sleep만 쓰면 취소 전파가 막힙니다. 요청, DB, 외부 호출처럼 수명이 있는 작업은 context를 먼저 기준으로 잡는 편이 안전합니다.
참고 링크
2 sources