如何在Go语言中迭代遍历一个time.Tick通道?

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

How do I iterate over a go time.Tick channel?

问题

我在使用time.Tick时遇到了困难。我期望这段代码在1秒后打印"hi" 10次,然后退出,但实际上它卡住了:

ticker := time.NewTicker(100 * time.Millisecond)
time.AfterFunc(time.Second, func () {
    ticker.Stop()
})

for _ = range ticker.C {
    go fmt.Println("hi")
}

查看源代码(http://golang.org/src/time/tick.go?s=1319:1342#L35),我发现当调用Stop()时,通道并没有关闭。在这种情况下,如何以惯用方式迭代ticker通道呢?

英文:

I'm having difficulty using time.Tick. I expect this code to print "hi" 10 times then quit after 1 second, but instead it hangs:

ticker := time.NewTicker(100 * time.Millisecond)
time.AfterFunc(time.Second, func () {
	ticker.Stop()
})

for _ = range ticker.C {
	go fmt.Println("hi")
}

https://play.golang.org/p/1p6-ViSvma

Looking at the source, I see that the channel isn't closed when Stop() is called. In that case, what is the idiomatic way to iterate over the ticker channel?

答案1

得分: 3

你是对的,停止时ticker的通道并没有关闭,这在文档中有说明:

Stop关闭一个ticker。在Stop之后,不会再发送任何tick。Stop不会关闭通道,以防止从通道中读取时出现错误。

我认为ticker更多的是“点火并忘记”的方式,即使你想要停止它,你甚至可以让该例程永远挂起(当然这取决于你的应用程序)。

如果你真的需要一个有限的ticker,你可以使用一些技巧并提供一个单独的通道(参考ThunderCat的回答),但我会提供自己的ticker实现。这应该相对容易,并且可以让你灵活地控制其行为,比如决定在缺少tick时要传递什么到通道,或者决定在读取者落后时要做什么。

以下是我的示例代码:

func finiteTicker(n int, d time.Duration) <-chan time.Time {
    ch := make(chan time.Time, 1)
    go func() {
        for i := 0; i < n; i++ {
            time.Sleep(d)
            ch <- time.Now()
        }
        close(ch)
    }()
    return ch
}

func main() {    
    for range finiteTicker(10, 100*time.Millisecond) {
        fmt.Println("hi")
    }
}

你可以在这里查看代码运行的示例:http://play.golang.org/p/ZOwJlM8rDm

英文:

You're right, ticker's channel is not being closed on stop, that's stated in a documentation:

> Stop turns off a ticker. After Stop, no more ticks will be sent. Stop does not close the channel, to prevent a read from the channel succeeding incorrectly.

I believe ticker is more about fire and forget and even if you want to stop it, you could even leave the routine hanging forever (depends on your application of course).

If you really need a finite ticker, you can do tricks and provide a separate channel (per ThunderCat's answer), but what I would do is providing my own implementation of ticker. This should be relatively easy and will give you flexibility with its behaviour, things like what to pass on the channel or deciding what to do with missing ticks (i.e. when reader is falling behind).

My example:

func finiteTicker(n int, d time.Duration) &lt;-chan time.Time {
	ch := make(chan time.Time, 1)
	go func() {
		for i := 0; i &lt; n; i++ {
			time.Sleep(d)
			ch &lt;- time.Now()
		}
		close(ch)
	}()
	return ch
}

func main() {	
	for range finiteTicker(10, 100*time.Millisecond) {
		fmt.Println(&quot;hi&quot;)
	}
}

http://play.golang.org/p/ZOwJlM8rDm

答案2

得分: 1

我也在IRC上询问了,并从@Tv`那里得到了一些有用的见解。

尽管 timer.Ticker 看起来应该是 Go pipeline 的一部分,但它实际上与 pipeline 的习惯用法并不兼容:

> 这是构建 pipeline 的准则:
>
> - 当所有发送操作完成时,阶段关闭其出站通道。
> - 阶段从入站通道接收值,直到这些通道关闭或发送者被解除阻塞。
>
> Pipeline 通过确保有足够的缓冲区来发送所有值,或者通过在接收者可能放弃通道时显式地通知发送者来解除发送者的阻塞。

这种不一致的原因似乎主要是为了支持以下习惯用法:

for {
    select {
    case &lt;-ticker.C:
        // 做一些事情
    case &lt;-done:
        return
    }
}

我不知道为什么会这样,也不知道为什么没有使用 pipeline 的习惯用法:

for {
    select {
    case _, ok := &lt;-ticker.C:
        if ok {
            // 做一些事情
        } else {
            return
        }
    }
}

(或更简洁地)

for _ = range ticker.C {
    // 做一些事情
}

但这就是 Go 的方式 如何在Go语言中迭代遍历一个time.Tick通道?

英文:

I asked on IRC as well, at got some useful insight from @Tv`.

Despite timer.Ticker looking like it should be part of a go pipeline, it does not actually play well with the pipeline idioms:

> Here are the guidelines for pipeline construction:
>
> - stages close their outbound channels when all the send operations are done.
> - stages keep receiving values from inbound channels until those channels are closed or the senders are unblocked.
>
> Pipelines unblock senders either by ensuring there's enough buffer for all the values that are sent or by explicitly signalling senders when the receiver may abandon the channel.

The reason for this inconsistency appears to be primarily to support the following idiom:

for {
    select {
    case &lt;-ticker.C:
        // do something
    case &lt;-done:
        return
    }
}

I don't know why this is the case, and why the pipelining idiom wasn't used:

for {
    select {
    case _, ok := &lt;-ticker.C:
        if ok {
            // do something
        } else {
            return
        }
    }
}

(or more cleanly)

for _ = range ticker.C {
    // do something
}

But this is the way go is 如何在Go语言中迭代遍历一个time.Tick通道?

huangapple
  • 本文由 发表于 2015年6月27日 07:26:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/31083572.html
匿名

发表评论

匿名网友

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

确定