This is a creation in Article, where the information may have evolved or changed.
Problem description
Now we have created a timer that can do something regularly and turn it off when the execution time expires. For example, you need to collect a week's log, create a timed task to collect logs, execute every 5 seconds, and stop this scheduled task after a week.
Standard library Ticker
Standard library provides the ticker class, the main function is to do something periodically and repeatedly, if there is no set timeout, it will continue to execute. The common wording is as follows:
t := time.NewTicker(3 * time.Second)timeout := time.After(10 * time.Second)go func() { for { <-t.C ... } }()<-timeout...
注意到这个Ticker对象是无法关闭的
, OK, you may find that the ticker class provides the Stop method. But let's see what happens if you turn off T.
package mainimport ( "fmt" "time")func DoTickerWork(res chan interface{}, timeout <-chan time.Time) { t := time.NewTicker(3 * time.Second) go func() { defer close(res) i := 1 for { <-t.C fmt.Printf("start %d th worker\n", i) res <- i i++ } }() <-timeout t.Stop() return}func main() { res := make(chan interface{}, 10000) timeout := time.After(10 * time.Second) DoTickerWork(res, timeout) for v := range res { fmt.Println(v) }}
Intuitively, the new goroutine in the process of waiting, the main thread will turn off the timer, there seems to be no bug, but the output is this:
$go run ticker.go start 1 th workerstart 2 th workerstart 3 th worker123fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan receive]:main.main() /home/gepin.zs/go/src/timer/ticker.go:29 +0xadgoroutine 6 [chan receive]:main.DoTickerWork.func1(0xc42006c060, 0xc4200161c0) /home/gepin.zs/go/src/timer/ticker.go:14 +0x8ecreated by main.DoTickerWork /home/gepin.zs/go/src/timer/ticker.go:19 +0x60exit status 2
This indicates that the stop method of the ticker object is the 并没有关掉
ticker channel, but the 阻止了channel的数据写入
goroutine task is still in progress, but the <-t.c has been blocked and there is a deadlock situation. It may be said that call Close (T.C) is possible, but the compilation will error: Cannot close receive-only channel, because T. C is a read-only queue and cannot call the Close method.
How to Solve
Don't think stop can turn off ticker, we can create a new channel with the name done, the cache size is 1,goroutine inside the Select, and then try to get timeout, if it can be fetched, the description has triggered the timeout, and then close ( Done), this time the task ends, the main thread return. The code is as follows:
package mainimport ( "fmt" "time")func DoTickerWork(res chan interface{}, timeout <-chan time.Time) { t := time.NewTicker(3 * time.Second) done := make(chan bool, 1) go func() { defer close(res) i := 1 for { select { case <-t.C: fmt.Printf("start %d th worker\n", i) res <- i i++ case <-timeout: close(done) return } } }() <-done return}func main() { res := make(chan interface{}, 10000) timeout := time.After(10 * time.Second) DoTickerWork(res, timeout) for v := range res { fmt.Println(v) }}
Program returns results
$go run ticker.gostart 1 th workerstart 2 th workerstart 3 th worker123