猿问

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

TimeOutTime在以下场景中,网络实体在执行特定任务之前总是等待几秒钟X。假设这个时间为TimerTTimeOutTime如果实体收到一组外部消息,则在此等待几秒钟期间,它应该再次将其重置TimerTTimeOutTime。如果没有收到外部消息,则预期行为如下:

  1. 计时器已过期

  2. 执行任务 X

  3. 再次将计时器重置为TimeOutTime

reset我的意思是,停止计时器并重新开始)

为了模拟场景,我在 Go 中编写了以下代码。


package main


import (

    "log"

    "math/rand"

    "sync"

    "time"

)


const TimeOutTime = 3

const MeanArrivalTime = 4


func 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 RACE

Write 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 +0x18f


Previous 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 +0x80


Goroutine 8 (running) created at:

  main.main()

      /home/deka/Academic/go/src/main/test.go:20 +0x1d3


Goroutine 9 (running) created at:

  main.main()

      /home/deka/Academic/go/src/main/test.go:35 +0x1f5

==================

然后我使用 Mutex 将Reset()调用包装在 Mutex 中。


慕虎7371278
浏览 90回答 3
3回答

德玛西亚99

time.After我使用函数简化了代码:package mainimport (&nbsp; &nbsp; "log"&nbsp; &nbsp; "math/rand"&nbsp; &nbsp; "time")const TimeOutTime = 3const MeanArrivalTime = 4func main() {&nbsp; &nbsp; const interval = time.Second * TimeOutTime&nbsp; &nbsp; // channel for incoming messages&nbsp; &nbsp; var incomeCh = make(chan struct{})&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // On each iteration new timer is created&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; select {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case <-time.After(interval):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time.Sleep(time.Second)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log.Println("Do task")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case <-incomeCh:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log.Println("Handle income message and move to the next iteration")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }()&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time.Sleep(time.Duration(rand.Intn(MeanArrivalTime)) * time.Second)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // generate incoming message&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; incomeCh <- struct{}{}&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }()&nbsp; &nbsp; // prevent main to stop for a while&nbsp; &nbsp; <-time.After(10 * time.Second)}注意:After等待持续时间过去,然后在返回的通道上发送当前时间。它相当于NewTimer(d).C。在定时器触发之前,垃圾收集器不会恢复底层定时器。如果效率是一个问题,请改用并在不再需要计时器时NewTimer 调用。Timer.Stop

繁星coding

您可能会考虑不同的整体设计。例如,假设我们编写了一个名为 Deadliner 的例程或接口——如果你愿意,它可以成为自己的包,或者只是一个接口,我们将看到与 Go 已有的东西非常相似——描述了它的工作/合同这边走:Deadliner 的用户可以随时创建截止日期。Deadliner 会一直等到最后期限出现,然后将最后期限标记为已出现。任何 Go 例程都可以随时取消 Deadliner。这会将截止日期标记为已取消,因此等待它的任何人都将停止等待,并且可以判断他们停止等待的原因是“取消”(而不是“过期”)。它也有助于清理 gc 的资源,以防你创建了很多 Deadliner,然后在它们的超时触发之前丢弃它们。现在在您的最高级别,在您开始等待消息之前,您只需设置一个截止日期。这不是一个计时器(即使它可能在内部使用一个),它只是一个 Deadliner 实例。然后你等待两个事件之一:d, cancel = newDeadline(when)for {&nbsp; &nbsp; select {&nbsp; &nbsp; case <-d.Done():&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Deadline expired.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // ... handle it ...&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; d, cancel = newDeadline(when) // if/as appropriate&nbsp; &nbsp; case m := <-msgC:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // got message - cancel existing deadline and get new one&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cancel()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; d, cancel = newDeadline(when)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // ... handle the message&nbsp; &nbsp; }}现在我们只注意到 Go 已经有了这个:它在 package 中context。 d是一个上下文;newDeadline是context.WithDeadline或context.WithTimeout(取决于您是要自己计算截止时间,还是让超时代码为“现在”添加持续时间)。无需摆弄计时器和时间滴答通道,也无需分拆您自己的单独 goroutine。如果最后期限没有在单个消息上重置,而是在特定消息组合上重置,您只需将其写在您的case <-msgChan部分中。如果当前没有通过通道接收消息,请通过将消息放入通道来实现,这样您就可以使用这个非常简单的等待截止日期或消息模式。

摇曳的蔷薇

假设你有:t.Stop()t.Reset()如果计时器在调用之前停止并耗尽Stop,则可以正常工作。Stop如果同时停止计时器和计时器滴答声,问题就会显现出来。然后你可能会遇到一个停止的计时器,一个 goroutine 等待写入t.C通道。Stop如果仍有一个 goroutine 等待写入,则返回 false ,t.C并且您必须从中读取。否则,你将让那个 goroutine 无限期地在那里等待。因此,正如您已经观察到的,您必须这样做:if !t.Stop() {&nbsp; &nbsp; <-t.C}t.Reset(d)但是,即便如此,我认为您的解决方案存在缺陷,因为使用了异步重置。相反,请尝试为每个模拟事件使用一个新计时器。
随时随地看视频慕课网APP

相关分类

Go
我要回答