在哪里创建Ticker有关系吗?

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

Does it matter where I create the Ticker?

问题

两种方式在线程安全性方面有一些区别,特别是当函数中的工作时间超过一个tick的周期时。

第一种方式中,我们使用了一个已经创建好的Ticker,并在一个单独的goroutine中循环读取ticker.C的值。这种方式下,我们需要在工作完成后手动调用ticker.Stop()来停止Ticker的运行。

第二种方式中,我们在goroutine内部创建了一个新的Ticker,并在循环中读取ticker.C的值。这种方式下,我们可以在循环条件中直接使用ticker.C来触发每次循环,而不需要手动调用ticker.Stop()来停止Ticker的运行。

当工作时间超过一个tick的周期时,第二种方式可能更可取,因为它可以在每次循环时重新创建一个新的Ticker,以确保每个tick都能被触发。而第一种方式中,如果工作时间超过一个tick的周期,可能会导致tick被丢弃。

需要注意的是,第二种方式中的ticker变量是在goroutine内部定义的局部变量,而不是外部的全局变量。这意味着在goroutine外部无法直接访问和停止这个Ticker。如果需要在外部停止Ticker的运行,仍然需要使用第一种方式,并在工作完成后手动调用ticker.Stop()。

总结起来,第一种方式更适合需要在外部控制Ticker运行和停止的情况,而第二种方式更适合在每次循环时重新创建Ticker的情况,特别是当工作时间可能超过一个tick的周期时。

英文:

Is there any difference between

ticker := time.NewTicker(1 * time.Second)

go func() {
	for _ = range ticker.C {
		fmt.Print("Tick")
	}
}()

time.Sleep(3)
ticker.Stop()

and

var ticker *time.Ticker

go func() {
	ticker = time.NewTicker(1 * time.Second)
	for _ = range ticker.C {
		fmt.Print("Tick")
	}
}()

time.Sleep(3)
ticker.Stop()

in terms of thread-safeness, especially when the work in the function takes longer than a tick's period?

I am asking because (when stopping the Ticker is not required) the latter can be shortened to

go func() {
	for ticker := time.NewTicker(1 * time.Second) ;; <-ticker.C {
		fmt.Print("Tick")
	}
}()

while the former cannot. This form has the additional advantage that the first tick is triggered right away.

答案1

得分: 2

第二个版本的代码是错误的:它存在竞态条件。

var ticker *time.Ticker

go func() {
    ticker = time.NewTicker(1 * time.Second)
    for _ = range ticker.C {
        fmt.Print("Tick")
    }
}()

time.Sleep(3)
ticker.Stop()

在 goroutine 内部对 ticker 的赋值和在 ticker.Stop() 调用中使用 ticker 之间没有同步。

实际上,由于长时间的 time.Sleep(3),这几乎总是无害的,但是如果可能的话,应该避免这种竞态条件,因为即使它们今天是无害的,它们以后可能会引起麻烦。例如,如果你的代码不是使用 Sleep,而是使用一些需要不同时间的代码,如果那段代码恰好执行了非常短的时间,你可能会看到空指针异常。

因此,出于这个原因,我会始终使用你的代码的第一个版本(在 goroutine 外部创建 ticker 的版本)。

代码的第三个版本(在 goroutine 内部完全使用 ticker 的版本)也是不错的。如果可能的话,我会使用这个更短的代码版本,其中 ticker 在 goroutine 内部定义。代码的简洁性很好,而且我也喜欢外部代码根本不看到 ticker,这样代码的读者很容易理解 ticker 的作用范围。

英文:

The second version of the code is wrong: it has a race condition.

var ticker *time.Ticker

go func() {
	ticker = time.NewTicker(1 * time.Second)
	for _ = range ticker.C {
		fmt.Print("Tick")
	}
}()

time.Sleep(3)
ticker.Stop()

There's no synchronization between the assignment to ticker inside the goroutine and the use of ticker in the ticker.Stop() call.

In practice this will almost always be harmless because of the long time.Sleep(3), but such races should be avoided if possible because even if they're harmless today they may cause trouble later. For example, if instead of Sleep you have some code that takes a variable amount of time, you may see nil pointer panics if that code happens to take a very short amount of time.

So for that reason, I'd always use the first version of your code (the one that creates the ticker outside the goroutine).

The third version of the code (where the ticker is used entirely inside the goroutine) is also good. I'd definitely use this shorter version of the code where the ticker is defined inside the goroutine if that's possible. The shortness of the code is nice, but I also like that the code outside doesn't see the ticker at all, so it's easy for the reader of the code to understand the scope of the ticker.

huangapple
  • 本文由 发表于 2016年5月28日 12:13:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/37495107.html
匿名

发表评论

匿名网友

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

确定