๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด/Go

[Go] ์ผ์ • ์‹œ๊ฐ„๋งˆ๋‹ค ์Šค์ผ€์ค„๋Ÿฌ ๋™์ž‘ํ•˜๊ธฐ(Ticker)

by ์„œ์•„๋ž‘๐Ÿ˜ƒ 2025. 11. 7.

.

 


time.Ticker ๋˜๋Š” time.AfterFunc, time.Sleep ๋“ฑ์„ ์ด์šฉํ•ด ์ฃผ๊ธฐ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋Š” ์Šค์ผ€์ค„๋Ÿฌ๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์•„๋ž˜์—์„œ๋Š” ์‹ค๋ฌด์—์„œ ์ž์ฃผ ์“ฐ์ด๋Š” n์ดˆ ๊ฐ„๊ฒฉ์œผ๋กœ ๋ฐ˜๋ณต ์‹คํ–‰๋˜๋Š” ์ž‘์—… ํ”„๋กœ์„ธ์Šค๋ฅผ ๋‹จ๊ณ„์ ์œผ๋กœ ์ •๋ฆฌํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ์ ์ธ time.Ticker ์‚ฌ์šฉ ์˜ˆ์‹œ

package main

import (
    "fmt"
    "time"
)

func main() {
    ticker := time.NewTicker(5 * time.Second) // 5์ดˆ๋งˆ๋‹ค ๋ฐ˜๋ณต
    defer ticker.Stop()

    for {
        select {
        case t := <-ticker.C:
            fmt.Println("์ž‘์—… ์‹คํ–‰ ์‹œ๊ฐ:", t.Format("15:04:05"))
            runTask()
        }
    }
}

func runTask() {
    // ์‹ค์ œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง
    fmt.Println("์‹คํ–‰ ์ค‘... (API ํ˜ธ์ถœ, DB ์—…๋ฐ์ดํŠธ ๋“ฑ)")
}

ํ•ต์‹ฌ ํฌ์ธํŠธ

  • time.NewTicker(d)๋Š” ์ผ์ • ์ฃผ๊ธฐ๋กœ ์ฑ„๋„์— ์ด๋ฒคํŠธ๋ฅผ ๋ณด๋ƒ…๋‹ˆ๋‹ค.
  • for + select ๊ตฌ๋ฌธ์œผ๋กœ ์ฃผ๊ธฐ์  ์‹ ํ˜ธ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋ฉฐ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • defer ticker.Stop()์œผ๋กœ ์ข…๋ฃŒ ์‹œ ๋ฆฌ์†Œ์Šค ํ•ด์ œ.

 

๋ฐฑ๊ทธ๋ผ์šด๋“œ ๊ณ ๋ฃจํ‹ด์—์„œ ์‹คํ–‰ (์„œ๋ฒ„ ๋‚ด๋ถ€์šฉ)

package main

import (
    "fmt"
    "time"
)

func startScheduler(interval time.Duration, stopChan <-chan struct{}) {
    ticker := time.NewTicker(interval)
    defer ticker.Stop()

    for {
        select {
        case <-ticker.C:
            go func() {
                fmt.Println("๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ์‹œ์ž‘:", time.Now())
                runTask()
            }()
        case <-stopChan:
            fmt.Println("์Šค์ผ€์ค„๋Ÿฌ ์ข…๋ฃŒ ์š”์ฒญ")
            return
        }
    }
}

func runTask() {
    fmt.Println("์ž‘์—… ์ˆ˜ํ–‰ ์ค‘...")
    time.Sleep(2 * time.Second)
    fmt.Println("์ž‘์—… ์™„๋ฃŒ.")
}

func main() {
    stopChan := make(chan struct{})
    go startScheduler(10*time.Second, stopChan)

    // ์„œ๋ฒ„๊ฐ€ ๋Œ์•„๊ฐ€๋Š” ๋™์•ˆ ๊ณ„์† ์‹คํ–‰๋จ
    time.Sleep(35 * time.Second)
    close(stopChan)
}

ํŠน์ง•

  • ์Šค์ผ€์ค„ ์ž‘์—…์ด ๊ณ ๋ฃจํ‹ด์œผ๋กœ ๋น„๋™๊ธฐ ์‹คํ–‰๋˜์–ด, ๋‹ค์Œ ์ฃผ๊ธฐ์™€ ๊ฒน์น˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • stopChan์„ ํ†ตํ•ด graceful stop์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

 

3๋‹จ๊ณ„: ๊ณ ๊ธ‰ํ˜• (์ง€์—ฐ, ์˜ค๋ฅ˜ ๋ณต๊ตฌ ํฌํ•จ)

์‹ค๋ฌด์—์„œ๋Š” ์ž‘์—…์ด ์‹คํŒจํ–ˆ์„ ๋•Œ ์žฌ์‹œ๋„ ๋กœ์ง์„ ๋„ฃ๊ฑฐ๋‚˜, ์ž‘์—…์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋ฉด ๋‹ค์Œ ์ฃผ๊ธฐ๋ฅผ ์ง€์—ฐ์‹œํ‚ค๋Š” ๊ตฌ์กฐ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

func startSafeScheduler(interval time.Duration, stopChan <-chan struct{}) {
    ticker := time.NewTicker(interval)
    defer ticker.Stop()

    for {
        select {
        case <-ticker.C:
            func() {
                defer func() {
                    if r := recover(); r != nil {
                        fmt.Println("์ž‘์—… ์ค‘ panic ๋ฐœ์ƒ:", r)
                    }
                }()
                if err := runTask(); err != nil {
                    fmt.Println("์—๋Ÿฌ:", err)
                }
            }()
        case <-stopChan:
            fmt.Println("์Šค์ผ€์ค„๋Ÿฌ ์ข…๋ฃŒ")
            return
        }
    }
}

func runTask() error {
    fmt.Println("DB ์—…๋ฐ์ดํŠธ ๋˜๋Š” API ํ˜ธ์ถœ ์ค‘...")
    // ์—ฌ๊ธฐ์— ์‹ค์ œ ๋กœ์ง
    return nil
}

์ด ๋ฐฉ์‹์€:

  • panic ๋ฐœ์ƒ ์‹œ ๋ณต๊ตฌ ๊ฐ€๋Šฅ
  • ๋‹ค์Œ ์ฃผ๊ธฐ์™€ ๊ฒน์น˜์ง€ ์•Š์Œ
  • ์‹คํŒจํ•œ ์ž‘์—… ๋กœ๊ทธ ๋‚จ๊น€

 

๋Œ€์ฒด ๋ฐฉ์‹ – Cron ์Šคํƒ€์ผ (github.com/robfig/cron/v3)

๋” ๋ณต์žกํ•œ ์Šค์ผ€์ค„์ด ํ•„์š”ํ•  ๋•Œ๋Š” cron ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

import (
    "github.com/robfig/cron/v3"
)

func main() {
    c := cron.New()
    c.AddFunc("@every 10s", func() { runTask() })
    c.Start()

    select {} // ํ”„๋กœ๊ทธ๋žจ ์œ ์ง€
}

 

@every 10s, 0 0 * * * ๋“ฑ ๋‹ค์–‘ํ•œ ์Šค์ผ€์ค„ ํ‘œํ˜„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

 

๋Œ“๊ธ€