广告位联系
返回顶部
分享到

golang1.23版本之前Timer Reset方法无法正确使用

Golang 来源:互联网 作者:佚名 发布时间:2025-01-28 23:35:58 人浏览
摘要

golang1.23 之前 Reset ?到底有什么问题 在golang 的 time.Reset 文档中有这么一句话,为了防止文档更新而导致内容变动,这里粘贴出来: Before Go 1.23, the only safe way to use Reset was to [Stop] and explicitly dr

golang1.23 之前 Reset ?到底有什么问题

在 golang 的 time.Reset 文档中有这么一句话,为了防止文档更新而导致内容变动,这里粘贴出来:

Before Go 1.23, the only safe way to use Reset was to [Stop] and explicitly drain the timer first. See the NewTimer documentation for more details. 在Go 1.23 之前,唯一安全使用Reset函数的方式是:在使用之前调用Stop函数并且明确的从timer的channel中抽取出东西。

虽然文档中已经告诉了正确使用的方式,但是实际上在真正的代码中无法达到这个要求,参考下方代码(来源代码来源):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

//consumer

    go func() {

        // try to read from channel, block at most 5s.

        // if timeout, print time event and go on loop.

        // if read a message which is not the type we want(we want true, not false),

        // retry to read.

        timer := time.NewTimer(time.Second * 5)

        for {

            // timer may be not active, and fired

            if !timer.Stop() {

                select {

                case <-timer.C: //try to drain from the channel,尝试抽取,由于使用select,因此这里可以保证:不阻塞 + 一定抽取成功

                default:

                }

            }

            timer.Reset(time.Second * 5)  //重置定时器

            select {

            case b := <-c:

                if b == false {

                    fmt.Println(time.Now(), ":recv false. continue")

                    continue

                }

                //we want true, not false

                fmt.Println(time.Now(), ":recv true. return")

                return

            case <-timer.C:

                fmt.Println(time.Now(), ":timer expired")

                continue

            }

        }

    }()

在上面的代码中,我们按照文档的要求,在 timer.Reset ?之前已经调用了 Stop ?函数,且如果 Stop 成功(返回 true),还尝试抽取 timer,看起来似乎没问题的代码中仍然存在问题。

问题的关键在于:当 Ticket 触发的时候,设置定时器状态的操作和发送 channel 的操作并不是原子的,见 runOneTimer 函数。

异常情况:尝试抽取 channel 在 发送 channel 之前,这样会导致 Reset 之前并没有真正的清空 timer,后一次的 timer.C 还没到触发时间就异常的触发了!



 

golang1.23 之前到底应该如何正确的使用 Reset?

实际上简洁点就这么写,每次一个新的局部变量 Timer? 结构体没压力,非要复用使用 Reset 的可读性太差了,对维护者不友好,而且习惯了不好的写法,哪天一不小心就写出问题了~

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

go func() {

        for {

            func() {

                timer := time.NewTimer(time.Second * 2)

                defer timer.Stop()

 

                select {

                case b := <-c:

                    if !b {

                        fmt.Println(time.Now(), "work...")

                    }

                case <-timer.C: // BBB: normal receive from channel timeout event

                    fmt.Println(time.Now(), "timeout")

                }

            }()

        }

    }()

参考:

https://tonybai.com/2016/12/21/how-to-use-timer-reset-in-golang-correctly/

https://www.v2ex.com/t/794283


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 :
相关文章
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计