gpt4 book ai didi

在选择循环中重置 timer.NewTimer

转载 作者:行者123 更新时间:2023-12-05 06:05:20 24 4
gpt4 key购买 nike

我有一个场景,我正在处理 channel 上的事件,其中一个事件是需要在特定时间范围内发生的心跳。不是心跳的事件将继续消耗计时器,但是无论何时收到心跳,我都想重置计时器。显而易见的方法是使用 time.NewTimer .

例如:

func main() {
to := time.NewTimer(3200 * time.Millisecond)
for {
select {
case event, ok := <-c:
if !ok {
return
} else if event.Msg == "heartbeat" {
to.Reset(3200 * time.Millisecond)
}
case remediate := <-to.C:
fmt.Println("do some stuff ...")
return
}
}
}

请注意 time.Ticker不会在这里工作,因为只有在未收到心跳时才应触发补救措施,而不是每次都收到。

上述解决方案适用于我尝试过的少数低容量测试,但是我遇到了一个 Github 问题,表明 resetting a Timer which has not fired is a no-no .此外,文档指出:

Reset should be invoked only on stopped or expired timers with drained channels. If a program has already received a value from t.C, the timer is known to have expired and the channel drained, so t.Reset can be used directly. If a program has not yet received a value from t.C, however, the timer must be stopped and—if Stop reports that the timer expired before being stopped—the channel explicitly drained:

if !t.Stop() {
<-t.C
}
t.Reset(d)

这让我停顿了一下,因为它似乎准确地描述了我正在尝试做的事情。我正在重置 Timer每当收到心跳时,在它被触发之前。我对 Go 的经验还不够丰富,无法理解整篇文章,但看起来我可能正走在一条危险的道路上。

我想到的另一个解决方案是简单地替换 Timer每当心跳发生时使用一个新的,例如:

else if event.Msg == "heartbeat" {
to = time.NewTimer(3200 * time.Millisecond)
}

起初我担心重新绑定(bind)to = time.NewTimer(3200 * time.Millisecond)在选择中不可见:

For all the cases in the statement, the channel operands of receive operations and the channel and right-hand-side expressions of send statements are evaluated exactly once, in source order, upon entering the "select" statement. The result is a set of channels to receive from or send to, and the corresponding values to send.

但在这种特殊情况下,由于我们处于循环中,我希望在每次迭代时我们重新输入选择,因此新绑定(bind)应该是可见的。这是一个公平的假设吗?

我意识到那里有类似的问题,我已经尝试阅读相关的帖子/文档,但我是 Go 的新手,只是想确保我在这里理解正确。

所以我的问题是:

  • 我用的是timer.Reset()不安全,还是 Github 问题中提到的案例突出了其他不适用于此处的问题?文档中的解释含糊不清,还是我只需要更多 Go 经验?

  • 如果它不安全,我提出的第二个解决方案是否可以接受(在每次迭代时重新绑定(bind)计时器)。

附录


进一步阅读后,问题中概述的大多数陷阱都描述了计时器已经触发(将结果放在 channel 上)的场景,并且在触发之后一些其他进程尝试重置它。对于这种狭窄的情况,我理解需要使用 !t.Stop() 进行测试因为 Stop 的错误返回表明计时器已经触发,因此必须在调用 Reset 之前耗尽。

我仍然不明白的是,为什么需要调用 t.Stop()t.Reset() 之前, 当Timer尚未开火。据我所知,没有一个例子涉及到这一点。

最佳答案

What I still do not understand, is why it is necessary to call t.Stop() prior to t.Reset(), when the Timer has yet to fire.

“当计时器尚未触发时”位在这里很关键。计时器在一个单独的 go 例程(runtime 的一部分)中触发,这可能随时发生。您无法知道在您调用 to.Reset(3200 * time.Millisecond) 时计时器是否已触发。 (它甚至可能在该函数运行时触发!)。

这是一个演示这一点的示例,它与您正在尝试的有点相似(基于 this ):


func main() {
eventC := make(chan struct{}, 1)
go keepaliveLoop(eventC )

// Reset the timer 1000 (approx) times; once every millisecond (approx)
// This should prevent the timer from firing (because that only happens after 2 ms)
for i := 0; i < 1000; i++ {
time.Sleep(time.Millisecond)
// Don't block if there is already a reset request
select {
case eventC <- struct{}{}:
default:
}
}
}

func keepaliveLoop(eventC chan struct{}) {
to := time.NewTimer(2 * time.Millisecond)

for {
select {
case <-eventC:
//if event.Msg == "heartbeat"...
time.Sleep(3 * time.Millisecond) // Simulate reset work (delay could be partly dur to whatever is triggering the
to.Reset(2 * time.Millisecond)
case <-to.C:
panic("this should never happen")
}
}
}

playground 中尝试.

由于 time.Sleep(3 * time.Millisecond),这可能显得做作但这只是为了始终如一地证明这个问题。您的代码可能在 99.9% 的时间内都有效,但事件和计时器 channel 总是有可能在 select 之前触发。正在运行(其中 random case 将运行)或当 case event, ok := <-c: 中的代码运行时 block 正在运行(包括 Reset() 正在进行中)。发生这种情况的结果是意外调用 remediate。代码(这可能不是什么大问题)。

幸运的是解决这个问题相对容易(遵循 documentation 中的建议):

time.Sleep(3 * time.Millisecond) // Simulate reset work (delay could be partly dur to whatever is triggering the
if !to.Stop() {
<-to.C
}
to.Reset(2 * time.Millisecond)

the playground 中试试这个.

这是有效的,因为 to.Stop returns “如果调用停止计时器则为 true,如果计时器已经过期或已停止则为 false”。请注意,如果 timer is used in multiple go-routines,事情会变得更加复杂。 “这不能与来自定时器 channel 的其他接收或对定时器停止方法的其他调用同时完成”,但在您的用例中情况并非如此。

Is my use of timer.Reset() unsafe, or are the cases mentioned in the Github issue highlighting other problems which are not applicable here?

是的——这是不安全的。然而,影响相当低。事件到达和计时器触发需要几乎同时发生,在这种情况下,运行 remediate代码可能不是一个大问题。请注意,修复非常简单(根据文档)

If it is unsafe, is my second proposed solution acceptable (rebinding the timer on each iteration).

您提出的第二个解决方案也有效(但请注意,垃圾收集器在触发或停止之前无法释放计时器,如果您快速创建计时器,这可能会导致问题)。

注意:回复@JotaSantos 的建议

Another thing that could be done is to add a select when draining <-to.C (on the Stop "if") with a default clause. That would prevent the pause.

参见 this comment有关为什么这可能不是一个好方法的详细信息(在您的情况下也没有必要)。

关于在选择循环中重置 timer.NewTimer,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66037676/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com