Go의 타이머와 티커에 대한 실용적인 가이드
Olivia Novak
Dev Intern · Leapcell

서문
일상적인 개발에서 일부 작업의 실행을 지연시키거나 주기적으로 실행해야 하는 상황에 직면할 수 있습니다. 이때 Go에서 타이머를 사용해야 합니다.
Go에는 두 가지 유형의 타이머가 있습니다. time.Timer
(일회성 타이머)와 time.Ticker
(주기적 타이머)입니다. 이 글에서는 두 가지 유형의 타이머를 모두 소개합니다.
타이머: 일회성 타이머
타이머는 미래의 특정 시간에 한 번 작업을 수행하는 데 사용되는 일회성 타이머입니다.
기본 사용법
타이머를 만드는 방법에는 두 가지가 있습니다.
NewTimer(d Duration) *Timer
: 이 함수는 타이머가 만료되기 전에 얼마나 기다려야 하는지를 나타내는time.Duration
유형의 매개변수d
(시간 간격)를 허용합니다.NewTimer
는 내부적으로 채널C
를 유지 관리하는 새 타이머를 반환합니다. 타이머가 작동하면 현재 시간이 채널C
로 전송됩니다.AfterFunc(d Duration, f func()) *Timer
: 지정된 시간 간격d
와 콜백 함수f
를 허용합니다. 이 함수는 새 타이머를 반환하고 타이머가 만료되면 채널C
를 통해 신호를 보내는 대신f
를 직접 호출합니다. 타이머의Stop
메서드를 호출하면 타이머가 중지되고f
의 실행이 취소됩니다.
다음 코드는 NewTimer
와 AfterFunc
를 사용하여 타이머를 만들고 기본적으로 사용하는 방법을 보여줍니다.
package main import ( "fmt" "time" ) func main() { // NewTimer를 사용하여 타이머를 만듭니다. timer := time.NewTimer(time.Second) go func() { select { case <-timer.C: fmt.Println("timer fired!") } }() // AfterFunc를 사용하여 다른 타이머를 만듭니다. time.AfterFunc(time.Second, func() { fmt.Println("timer2 fired!") }) // 기본 고루틴은 타이머 출력을 볼 수 있도록 2초 동안 기다립니다. time.Sleep(time.Second * 2) }
위 코드의 출력은 다음과 같습니다.
timer fired! timer2 fired!
다음은 코드에 대한 단계별 설명입니다.
NewTimer
를 사용하여 타이머를 만든 다음 새 고루틴에서C
속성을 수신하여 타이머가 작동할 때까지 기다립니다.AfterFunc
를 사용하여 다른 타이머를 만들고 타이머 만료 이벤트를 처리할 콜백 함수를 지정합니다.- 기본 고루틴은 타이머의 작동 정보를 인쇄할 수 있을 만큼 충분히 기다립니다.
메서드 세부 정보
Reset
Reset(d Duration) bool
: 이 메서드는 타이머의 만료 시간을 재설정하는 데 사용되며 기본적으로 다시 활성화합니다. 만료되기 전에 타이머가 얼마나 기다려야 하는지를 나타내는 time.Duration
유형의 매개변수 d
를 허용합니다.
또한 이 메서드는 bool
값을 반환합니다.
- 타이머가 활성 상태이면
true
를 반환합니다. - 타이머가 이미 만료되었거나 중지된 경우
false
를 반환합니다(참고:false
는 재설정이 실패했음을 의미하지 않으며 타이머의 현재 상태만 나타냅니다).
다음은 코드 예제입니다.
package main import ( "fmt" "time" ) func main() { timer := time.NewTimer(5 * time.Second) // 첫 번째 재설정: 타이머가 활성 상태이므로 true를 반환합니다. b := timer.Reset(1 * time.Second) fmt.Println(b) // true second := time.Now().Second() select { case t := <-timer.C: fmt.Println(t.Second() - second) // 1s } // 두 번째 재설정: 타이머가 이미 만료되었으므로 false를 반환합니다. b = timer.Reset(2 * time.Second) fmt.Println(b) // false second = time.Now().Second() select { case t := <-timer.C: fmt.Println(t.Second() - second) // 2s } }
코드의 출력은 다음과 같습니다.
true 1 false 2
단계별 설명:
- 5초 후에 만료되도록 설정된 타이머를 만듭니다.
Reset
메서드를 즉시 호출하여 1초 후에 만료되도록 설정합니다. 타이머가 아직 활성 상태(만료되지 않음)이므로Reset
은true
를 반환합니다.select
문은 타이머가 만료될 때까지 기다리고 실제 경과된 시간(약 1초)을 인쇄합니다.- 타이머가 다시 재설정되어 이번에는 2초 후에 만료되도록 설정됩니다. 타이머가 이미 만료되었으므로
Reset
은false
를 반환합니다. select
문은 타이머가 만료될 때까지 다시 기다리고 경과된 시간(약 2초)을 인쇄합니다.
Stop
Stop() bool
: 이 메서드는 타이머를 중지하는 데 사용됩니다. 타이머가 성공적으로 중지되면 true
를 반환합니다. 타이머가 이미 만료되었거나 중지된 경우 false
를 반환합니다. 참고: Stop
작업은 채널 C
를 닫지 않습니다.
다음은 코드 예제입니다.
package main import ( "fmt" "time" ) func main() { timer := time.NewTimer(3 * time.Second) // 타이머가 작동하기 전에 중지하므로 true를 반환합니다. stop := timer.Stop() fmt.Println(stop) // true stop = timer.Stop() // 타이머를 다시 중지하면 이미 중지되었으므로 false를 반환합니다. fmt.Println(stop) // false }
출력은 다음과 같습니다.
true false
단계별 설명:
- 3초 후에 작동하도록 설정된 타이머를 만듭니다.
Stop
메서드를 즉시 호출하여 타이머를 중지합니다. 타이머가 아직 작동하지 않았으므로Stop
은true
를 반환합니다.Stop
을 다시 호출하여 동일한 타이머를 중지하려고 시도합니다. 이미 중지되었으므로 이번에는Stop
이false
를 반환합니다.
Ticker: 주기적 타이머
Ticker는 고정된 간격으로 작업을 반복적으로 실행하는 데 사용되는 주기적 타이머입니다. 매 간격마다 현재 시간을 채널로 보냅니다.
기본 사용법
NewTicker
함수를 사용하여 새 Ticker 객체를 만들 수 있습니다. 이 함수는 time.Duration
매개변수 d
(간격)를 허용합니다.
다음은 예제입니다.
package main import ( "context" "fmt" "time" ) func main() { ticker := time.NewTicker(time.Second) defer ticker.Stop() timeout, cancelFunc := context.WithTimeout(context.Background(), time.Second*5) defer cancelFunc() go func() { for { select { case <-timeout.Done(): fmt.Println("timeout done") return case <-ticker.C: fmt.Println("ticker fired!") } } }() // 기본 고루틴은 타이머 출력을 볼 수 있도록 7초 동안 기다립니다. time.Sleep(time.Second * 7) }
코드의 출력은 다음과 같습니다.
ticker fired! ticker fired! ticker fired! ticker fired! ticker fired! timeout done
단계별 설명:
- 매초마다 작동하는 타이머를 만듭니다. 함수가 끝날 때 타이머가 정리되도록
defer ticker.Stop()
을 사용합니다. - 5초 후에 시간 초과되는 컨텍스트를 만듭니다.
cancelFunc
는 종료하기 전에 컨텍스트를 정리하는 데 사용됩니다. - 새 고루틴에서
select
문은 타이머의 채널(ticker.C
)과 컨텍스트의 완료 채널(timeout.Done()
)의 두 채널을 수신합니다. 타이머가 매초마다 작동하면 메시지를 인쇄합니다. 컨텍스트가 시간 초과되면(5초 후) 시간 초과 메시지를 인쇄하고 반환하여 고루틴을 종료합니다. - 기본 고루틴은
time.Sleep(time.Second * 7)
을 사용하여 7초 동안 기다려 타이머 작동 및 시간 초과 이벤트를 모두 관찰할 수 있도록 합니다.
select
로 ticker.C
를 수신할 뿐만 아니라 for range
루프를 사용할 수도 있습니다.
for range ticker.C {}
참고: Stop
메서드로 Ticker를 중지하더라도 해당 채널 C
는 닫히지 않습니다. 즉, for select
또는 for range
를 사용하여 ticker.C
를 수신하든 루프를 종료하는 다른 메커니즘(예: 컨텍스트 사용)이 필요합니다.
메서드 세부 정보
Reset
Reset(d Duration)
메서드는 티커를 중지하고 해당 기간을 지정된 기간으로 재설정하는 데 사용됩니다. 다음 틱은 새 기간이 경과한 후에 발생합니다. 새 간격을 나타내는 time.Duration
유형의 매개변수 d
를 허용합니다. 이 매개변수는 0보다 커야 합니다. 그렇지 않으면 Reset
메서드가 내부적으로 패닉 상태가 됩니다.
다음은 예제입니다.
package main import ( "time" ) func main() { ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() // 티커를 재설정합니다. ticker.Reset(1 * time.Second) second := time.Now().Second() for t := range ticker.C { // 1s fmt.Printf("Interval: %d seconds", t.Second()-second) break } }
코드의 출력은 다음과 같습니다.
Interval: 1 seconds
단계별 설명:
- 5초마다 작동하는 time.Ticker를 만듭니다.
Reset
메서드를 사용하여 간격을 5초에서 1초로 변경합니다.- 단일 루프에서 간격을 인쇄합니다. 예상 결과는 1초입니다.
Stop
Stop()
메서드는 티커를 중지하는 데 사용됩니다. Stop
을 호출한 후에는 더 이상 틱이 채널 C
로 전송되지 않습니다. 참고: Stop
작업은 채널 C
를 닫지 않습니다.
다음은 예제입니다.
package main import ( "fmt" "time" ) func main() { ticker := time.NewTicker(time.Second) quit := make(chan struct{}) // 종료 채널을 만듭니다. go func() { for { select { case <-ticker.C: fmt.Println("Ticker fired!") case <-quit: fmt.Println("Goroutine stopped!") return // 종료 신호를 받으면 루프를 종료합니다. } } }() time.Sleep(time.Second * 3) ticker.Stop() // 티커를 중지합니다. close(quit) // 종료 신호를 보냅니다. fmt.Println("Ticker stopped!") }
출력은 다음과 같습니다.
Ticker fired! Ticker fired! Ticker fired! Goroutine stopped! Ticker stopped!
- 매초마다 작동하는 time.Ticker 객체를 만듭니다. 동시에 실행 중인 고루틴에 중지 신호를 보내는 데 사용되는
chan struct{}
유형의 종료 채널이 도입됩니다. - 새 고루틴을 시작합니다. 이 고루틴에서 for-select 루프는 티커 작동(
case <-ticker.C
)과 종료 신호(case <-quit
)의 두 이벤트를 수신합니다. 티커가 작동할 때마다 메시지를 인쇄합니다. 종료 신호를 받으면 메시지를 인쇄하고 루프를 종료합니다. - 기본 고루틴에서
time.Sleep(time.Second * 3)
은 3초의 대기 시간을 시뮬레이션합니다. 이 시간 동안 티커가 몇 번 작동합니다. - 기본 고루틴은
Stop
을 호출하여 티커를 중지한 다음 종료 채널을 닫습니다. 고루틴은 종료 신호를 받고 메시지를 인쇄하고 루프를 종료합니다.
Stop
메서드는 채널 C
를 닫지 않으므로 다른 수단(예: 종료 신호)을 사용하여 리소스를 정리해야 합니다.
타이머와 티커의 주요 차이점
사용법:
- 타이머는 단일 지연 후에 실행되는 작업에 사용됩니다.
- 티커는 반복적으로 실행해야 하는 작업에 사용됩니다.
동작적 특성:
- 타이머는 지정된 지연 후에 한 번 활성화되어 단일 시간 값을 채널로 보냅니다.
- 티커는 지정된 간격으로 주기적으로 활성화되어 반복된 시간 값을 채널로 보냅니다.
제어 가능성:
- 타이머는 재설정(
Reset
메서드)하고 중지(Stop
메서드)할 수 있습니다.Reset
은 타이머의 작동 시간을 변경하는 데 사용됩니다. - 티커도 재설정(
Reset
메서드)하고 중지(Stop
메서드)할 수 있습니다.Reset
은 티커가 작동하는 간격을 변경하는 데 사용됩니다.
종료 작업:
- 타이머의
Stop
메서드는 타이머가 작동하지 않도록 하는 데 사용됩니다. 타이머가 이미 작동한 경우Stop
은 이미 채널로 전송된 시간 값을 제거하지 않습니다. - 티커의
Stop
메서드는 주기적 활성화를 중지하는 데 사용됩니다. 중지되면 더 이상 새 값이 채널로 전송되지 않습니다.
참고 사항
- 타이머와 티커 모두
Stop
메서드를 호출해도 해당C
채널은 닫히지 않습니다. 이 채널을 수신하는 다른 고루틴이 있는 경우 잠재적인 메모리 누수를 방지하려면 해당 고루틴을 수동으로 종료해야 합니다. 일반적으로 이러한 리소스 정리는context
를 사용하거나 종료 신호(채널로 구현됨)로 처리할 수 있습니다. - 티커가 작업을 완료한 후에는 관련 리소스를 해제하고 메모리 누수를 방지하기 위해
Stop
메서드를 호출해야 합니다. 제때에 티커를 중지하지 않으면 지속적인 리소스 점유가 발생할 수 있습니다.
요약
이 글에서는 Go의 타이머와 티커를 심층적으로 살펴보고, 이를 생성하는 방법, 기본 사용법, 관련 메서드를 자세히 소개했습니다. 또한 이 글에서는 이러한 두 가지 유형의 타이머 간의 주요 차이점을 요약하고 이를 사용할 때 염두에 두어야 할 고려 사항을 강조합니다.
Go 코드를 작성할 때는 애플리케이션 시나리오에 따라 적절한 타이머를 선택해야 합니다. 동시에 특히 타이머 작업을 마친 후 즉시 리소스를 해제하는 등 모범 사례를 따르는 것이 중요하며, 이는 잠재적인 메모리 누수를 방지하는 데 매우 중요합니다.
Go 프로젝트 호스팅을 위한 최고의 선택, Leapcell입니다.
Leapcell은 웹 호스팅, 비동기 작업 및 Redis를 위한 차세대 서버리스 플랫폼입니다.
다국어 지원
- Node.js, Python, Go 또는 Rust로 개발하세요.
무제한 프로젝트를 무료로 배포하세요.
- 사용량에 대해서만 지불하세요. 요청도 없고, 요금도 없습니다.
탁월한 비용 효율성
- 유휴 요금 없이 사용한 만큼만 지불하세요.
- 예: 평균 응답 시간 60ms에서 25달러로 694만 건의 요청을 지원합니다.
간소화된 개발자 경험
- 간편한 설정을 위한 직관적인 UI.
- 완전 자동화된 CI/CD 파이프라인 및 GitOps 통합.
- 실행 가능한 통찰력을 위한 실시간 지표 및 로깅.
간편한 확장성 및 고성능
- 쉬운 동시성 처리를 위한 자동 확장.
- 운영 오버헤드가 없으므로 구축에만 집중하세요.
설명서에서 자세히 알아보세요!
X에서 팔로우하세요: @LeapcellHQ