Maxim Samburskiy
Maxim Samburskiy

Reputation: 2001

Repeating countdown with different duration

I would like to make a countdown ticker with 2 different durations. What is the best way to do this? I try to do this:

s5 := time.Tick(5 * time.Second)
m5 := time.Tick(5 * time.Minute)

for {
    select {
        case t := <-s5:
            ...
        case t := <-m5:
            ...
    }
}

But I need ticker for different intervals:

5:00 -> 0:00
0:05 -> 0:00
5:00 -> 0:00
0:05 -> 0:00

Which is idiomatic way to do this?

Upvotes: 1

Views: 330

Answers (2)

JimB
JimB

Reputation: 109318

You could just call sleep if you want

dur := 1 * time.Second
nextDur := 3 * time.Second

for {
    time.Sleep(dur)
    dur, nextDur = nextDur, dur

    ...
}

Or alternate the durations in a time.Timer if you need to select. This is what I would personally stick with, since you don't need to worry about the offset between two timers skewing due to scheduling inconsistencies.

dur := 1 * time.Second
nextDur := 3 * time.Second

timer := time.NewTimer(dur)

for {
    select {
    case t := <-timer.C:
        dur, nextDur = nextDur, dur
        timer.Reset(dur)
        ...
    }
    ...
}

Or run 2 timers offset by the smaller interval

dur1 := 1 * time.Second
dur2 := 3 * time.Second

timer1 := time.NewTimer(dur1)
timer2 := time.NewTimer(dur1 + dur2)

for {
    select {
    case t := <-timer1.C:
        timer1.Reset(dur1 + dur2)
        fmt.Println("timer1:", t)
    case t := <-timer2.C:
        timer2.Reset(dur1 + dur2)
        fmt.Println("timer2:", t)
    }

}

And you could also run interleaved Tickers like you originally tried, but that requires a little more coordination to delay the start of one of them

dur1 := 1 * time.Second
dur2 := 3 * time.Second

ticker1 := time.NewTicker(dur1)
ticker2 := time.NewTicker(dur1 + dur2)

var once sync.Once
delayOnce := func() {
    ticker1.Stop()
    ticker1 = time.NewTicker(dur1 + dur2)
}

for  {
    select {
    case t := <-ticker1.C:
        once.Do(delayOnce)
        fmt.Println("ticker1:", t)
    case t := <-ticker2.C:
        fmt.Println("ticker2:", t)
    }

}

Upvotes: 3

icza
icza

Reputation: 417402

One solution is to have only 1 ticker which ticks in every 5 seconds. 5 minutes plus 5 seconds is 61*5 seconds. So the "period" is 61 ticks. Every 61th tick is the 5-minute mark, and every 61th+1 tick is a 5-sec mark. Since there is only one ticker, there is not even need for select:

c, count := time.Tick(5*time.Second), 1
for {
    <-c
    count++
    switch count % 61 {
    case 0:
        fmt.Println("5-min mark")
    case 1:
        fmt.Println("5-sec mark")
    }
}

Note: since count is initialized with 1, the first "task" will be the 5-min mark, executed after 5 min after start.

Another solution is to use a sequence of 2 time.Sleep() calls, first being 5 min, second being 5 seconds:

for {
    time.Sleep(5 * time.Minute)
    fmt.Println("5-min mark")
    time.Sleep(5 * time.Second)
    fmt.Println("5-sec mark")
}

But timing of this also depends on the task you execute. So either use the first solution or execute the tasks in separate goroutines so they don't interfere with the timing, e.g.:

for {
    time.Sleep(5 * time.Minute)
    go func () {
        fmt.Println("5-min mark")
    }
    time.Sleep(5 * time.Second)
    go func () {
        fmt.Println("5-sec mark")
    }
}

Upvotes: 1

Related Questions