为了账号安全,请及时绑定邮箱和手机立即绑定

在单独的 go 例程中重置计时器

在单独的 go 例程中重置计时器

Go
慕虎7371278 2022-05-18 16:00:04
TimeOutTime在以下场景中,网络实体在执行特定任务之前总是等待几秒钟X。假设这个时间为TimerT。TimeOutTime如果实体收到一组外部消息,则在此等待几秒钟期间,它应该再次将其重置TimerT为TimeOutTime。如果没有收到外部消息,则预期行为如下:计时器已过期执行任务 X再次将计时器重置为TimeOutTime(reset我的意思是,停止计时器并重新开始)为了模拟场景,我在 Go 中编写了以下代码。package mainimport (    "log"    "math/rand"    "sync"    "time")const TimeOutTime = 3const MeanArrivalTime = 4func main() {    rand.Seed(time.Now().UTC().UnixNano())    var wg sync.WaitGroup    t := time.NewTimer(time.Second * time.Duration(TimeOutTime))    wg.Add(1)    // go routine for doing timeout event    go func() {        defer wg.Done()        for {            t1 := time.Now()            <-t.C            t2 := time.Now()            // Do.. task X .. on timeout...            log.Println("Timeout after ", t2.Sub(t1))            t.Reset(time.Second * time.Duration(TimeOutTime))        }    }()    // go routine to simulate incoming messages ...    // second go routine    go func() {        for {            // simulates a incoming message at any time            time.Sleep(time.Second * time.Duration(rand.Intn(MeanArrivalTime)))            // once any message is received reset the timer to TimeOutTime seconds again            t.Reset(time.Second * time.Duration(TimeOutTime))        }    }()    wg.Wait()}-race使用标志运行此程序后,它显示DATA_RACE:==================WARNING: DATA RACEWrite at 0x00c0000c2068 by goroutine 8:  time.(*Timer).Reset()      /usr/local/go/src/time/sleep.go:125 +0x98  main.main.func1()      /home/deka/Academic/go/src/main/test.go:29 +0x18fPrevious write at 0x00c0000c2068 by goroutine 9:  time.(*Timer).Reset()      /usr/local/go/src/time/sleep.go:125 +0x98  main.main.func2()      /home/deka/Academic/go/src/main/test.go:42 +0x80Goroutine 8 (running) created at:  main.main()      /home/deka/Academic/go/src/main/test.go:20 +0x1d3Goroutine 9 (running) created at:  main.main()      /home/deka/Academic/go/src/main/test.go:35 +0x1f5==================然后我使用 Mutex 将Reset()调用包装在 Mutex 中。
查看完整描述

3 回答

?
德玛西亚99

TA贡献1770条经验 获得超3个赞

time.After我使用函数简化了代码:


package main


import (

    "log"

    "math/rand"

    "time"

)


const TimeOutTime = 3

const MeanArrivalTime = 4


func main() {

    const interval = time.Second * TimeOutTime

    // channel for incoming messages

    var incomeCh = make(chan struct{})


    go func() {

        for {

            // On each iteration new timer is created

            select {

            case <-time.After(interval):

                time.Sleep(time.Second)

                log.Println("Do task")

            case <-incomeCh:

                log.Println("Handle income message and move to the next iteration")

            }

        }

    }()


    go func() {

        for {

            time.Sleep(time.Duration(rand.Intn(MeanArrivalTime)) * time.Second)

            // generate incoming message

            incomeCh <- struct{}{}

        }

    }()


    // prevent main to stop for a while

    <-time.After(10 * time.Second)

}

注意:


After等待持续时间过去,然后在返回的通道上发送当前时间。它相当于NewTimer(d).C。在定时器触发之前,垃圾收集器不会恢复底层定时器。如果效率是一个问题,请改用并在不再需要计时器时NewTimer 调用。Timer.Stop


查看完整回答
反对 回复 2022-05-18
?
繁星coding

TA贡献1797条经验 获得超4个赞

您可能会考虑不同的整体设计。

例如,假设我们编写了一个名为 Deadliner 的例程或接口——如果你愿意,它可以成为自己的包,或者只是一个接口,我们将看到与 Go 已有的东西非常相似——描述了它的工作/合同这边走:

  • Deadliner 的用户可以随时创建截止日期。

  • Deadliner 会一直等到最后期限出现,然后将最后期限标记为已出现。

  • 任何 Go 例程都可以随时取消 Deadliner。这会将截止日期标记为已取消,因此等待它的任何人都将停止等待,并且可以判断他们停止等待的原因是“取消”(而不是“过期”)。它也有助于清理 gc 的资源,以防你创建了很多 Deadliner,然后在它们的超时触发之前丢弃它们。

现在在您的最高级别,在您开始等待消息之前,您只需设置一个截止日期。这不是一个计时器(即使它可能在内部使用一个),它只是一个 Deadliner 实例。然后你等待两个事件之一:

d, cancel = newDeadline(when)

for {

    select {

    case <-d.Done():

          // Deadline expired.

          // ... handle it ...

          d, cancel = newDeadline(when) // if/as appropriate

    case m := <-msgC:

          // got message - cancel existing deadline and get new one

          cancel()

          d, cancel = newDeadline(when)

          // ... handle the message

    }

}

现在我们只注意到 Go 已经有了这个:它在 package 中context。 d是一个上下文;newDeadline是context.WithDeadline或context.WithTimeout(取决于您是要自己计算截止时间,还是让超时代码为“现在”添加持续时间)。


无需摆弄计时器和时间滴答通道,也无需分拆您自己的单独 goroutine。


如果最后期限没有在单个消息上重置,而是在特定消息组合上重置,您只需将其写在您的case <-msgChan部分中。如果当前没有通过通道接收消息,请通过将消息放入通道来实现,这样您就可以使用这个非常简单的等待截止日期或消息模式。


查看完整回答
反对 回复 2022-05-18
?
摇曳的蔷薇

TA贡献1793条经验 获得超6个赞

假设你有:


t.Stop()

t.Reset()

如果计时器在调用之前停止并耗尽Stop,则可以正常工作。Stop如果同时停止计时器和计时器滴答声,问题就会显现出来。然后你可能会遇到一个停止的计时器,一个 goroutine 等待写入t.C通道。Stop如果仍有一个 goroutine 等待写入,则返回 false ,t.C并且您必须从中读取。否则,你将让那个 goroutine 无限期地在那里等待。


因此,正如您已经观察到的,您必须这样做:


if !t.Stop() {

    <-t.C

}

t.Reset(d)

但是,即便如此,我认为您的解决方案存在缺陷,因为使用了异步重置。相反,请尝试为每个模拟事件使用一个新计时器。


查看完整回答
反对 回复 2022-05-18
  • 3 回答
  • 0 关注
  • 89 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信