Golang中的Ticker停止行为

huangapple go评论82阅读模式
英文:

Ticker Stop behaviour in Golang

问题

如果我正在遍历一个ticker通道并调用stop(),通道会停止但不会关闭。

在这个例子中:

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)
}

运行结果为:

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

因此,该goroutine永远不会退出。有没有更好的处理这种情况的方法?我应该关心该goroutine永远不会退出吗?

英文:

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?

答案1

得分: 23

使用了第二个通道如Volker建议的。这是我最终运行的代码:

package main

import (
    "log"
    "time"
)

// 每个滴答运行一次函数
// 从函数中返回false以停止滴答器
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("滴答器停止")
        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("滴答")
        return true
    })

    time.Sleep(3 * time.Second)
    log.Println("停止滴答器")
    stop <- true
    time.Sleep(3 * time.Second)
}
英文:

Used a second channel as Volker suggested. This is what I ended up running with:

package main

import (
    &quot;log&quot;
    &quot;time&quot;
)

// 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(&quot;ticker stopped&quot;)
        for {
            select {
            case time := &lt;-ticker.C:
                if !work(time) {
                    stop &lt;- true
                }
            case &lt;-stop:
                return
            }
        }
    }()

    return stop
}

func main() {
    stop := Every(1*time.Second, func(time.Time) bool {
        log.Println(&quot;tick&quot;)
        return true
    })

    time.Sleep(3 * time.Second)
    log.Println(&quot;stopping ticker&quot;)
    stop &lt;- true
    time.Sleep(3 * time.Second)
}

答案2

得分: 15

在你的goroutine中,在ticker和done通道之间选择,并在第二个通道上发送信号“done”。

根据你真正想要做什么,可能存在更好的解决方案,但从简化的演示代码中很难判断。

英文:

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.

答案3

得分: 8

你可以这样做。

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)
}
英文:

you can do like this.

package main

import (
    &quot;fmt&quot;
    &quot;time&quot;
)

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 &lt;-ticker.C:
                f()
            case &lt;-done:
                fmt.Println(&quot;done&quot;)
                return
            }
        }
    }()
    return done
}

func main() {
    done := startTicker(func() {
        fmt.Println(&quot;tick...&quot;)
    })
    time.Sleep(5 * time.Second)
    close(done)
    time.Sleep(5 * time.Second)
}

答案4

得分: 0

如果你需要节省更多的空间,可以使用空结构体(struct{})的通道,它不占用内存。正如上面提到的,不要在其中发送任何东西,只需关闭通道,实际上发送的是零值。

英文:

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.

答案5

得分: 0

我认为这是一个很好的答案

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("做工作") },
		stop,
		func() {
			fmt.Println("在go协程中完成")
			wg.Done()
		},
	)

	time.Sleep(5 * time.Second)
	close(stop)
	wg.Wait()
	fmt.Println("在主函数中完成")
}
英文:

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 &lt;-t.C:
			work()
		case _, more := &lt;-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(&quot;do work&quot;) },
		stop,
		func() {
			fmt.Println(&quot;done in go routine&quot;)
			wg.Done()
		},
	)

	time.Sleep(5 * time.Second)
	close(stop)
	wg.Wait()
	fmt.Println(&quot;done in main&quot;)
}

huangapple
  • 本文由 发表于 2013年7月23日 05:28:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/17797754.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定