Reputation: 3334
If I am ranging over a ticker channel and call stop() the channel is stopped but not closed.
In this Example:
package main
import (
"time"
"log"
)
func main() {
ticker := time.NewTicker(1 * time.Second)
go func(){
for _ = range ticker.C {
log.Println("tick")
}
log.Println("stopped")
}()
time.Sleep(3 * time.Second)
log.Println("stopping ticker")
ticker.Stop()
time.Sleep(3 * time.Second)
}
Running produces:
2013/07/22 14:26:53 tick
2013/07/22 14:26:54 tick
2013/07/22 14:26:55 tick
2013/07/22 14:26:55 stopping ticker
So that goroutine never exits. Is there a better way to handle this case? Should I care that the goroutine never exited?
Upvotes: 25
Views: 40739
Reputation: 1
I think this is a nice answer
func doWork(interval time.Duration, work func(), stop chan struct{}, done func()) {
t := time.NewTicker(interval)
for {
select {
case <-t.C:
work()
case _, more := <-stop:
if !more {
done()
}
return
}
}
}
func Test_workTicker(t *testing.T) {
var wg sync.WaitGroup
wg.Add(1)
stop := make(chan struct{})
go doWork(
500*time.Millisecond,
func() { fmt.Println("do work") },
stop,
func() {
fmt.Println("done in go routine")
wg.Done()
},
)
time.Sleep(5 * time.Second)
close(stop)
wg.Wait()
fmt.Println("done in main")
}
Upvotes: 0
Reputation: 3334
Used a second channel as Volker suggested. This is what I ended up running with:
package main
import (
"log"
"time"
)
// Run the function every tick
// Return false from the func to stop the ticker
func Every(duration time.Duration, work func(time.Time) bool) chan bool {
ticker := time.NewTicker(duration)
stop := make(chan bool, 1)
go func() {
defer log.Println("ticker stopped")
for {
select {
case time := <-ticker.C:
if !work(time) {
stop <- true
}
case <-stop:
return
}
}
}()
return stop
}
func main() {
stop := Every(1*time.Second, func(time.Time) bool {
log.Println("tick")
return true
})
time.Sleep(3 * time.Second)
log.Println("stopping ticker")
stop <- true
time.Sleep(3 * time.Second)
}
Upvotes: 23
Reputation: 387
If you need to save more space, use channels of empty structs (struct{}
) which costs no memory. And as mentioned above, don't send something in it - just close, which actually sends zero value.
Upvotes: 0
Reputation: 191
you can do like this.
package main
import (
"fmt"
"time"
)
func startTicker(f func()) chan bool {
done := make(chan bool, 1)
go func() {
ticker := time.NewTicker(time.Second * 1)
defer ticker.Stop()
for {
select {
case <-ticker.C:
f()
case <-done:
fmt.Println("done")
return
}
}
}()
return done
}
func main() {
done := startTicker(func() {
fmt.Println("tick...")
})
time.Sleep(5 * time.Second)
close(done)
time.Sleep(5 * time.Second)
}
Upvotes: 9
Reputation: 42433
Signal "done" on a second channel and select in your goroutine between ticker and done channel.
Depending on what you really want to do a better solution might exist, but this is hard to tell from the reduced demo code.
Upvotes: 15