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

我是如何在这里混淆 goroutines 中变量和指针的作用域的?

我是如何在这里混淆 goroutines 中变量和指针的作用域的?

Go
ABOUTYOU 2021-11-08 10:23:41
我正在通过编写一个简单的程序来学习 Go,该程序同时从几个 http 服务器下载传感器数据文件。服务器上的传感器数据文件会定期刷新(30 秒或 2 分钟,取决于“来源”)。下载数据可能需要 100 毫秒到 10 秒的时间。所以我阅读了每个服务器(OriginContext)的一些配置。然后我为每个 OriginContext 启动一个控制器。每个控制器持续触发一个执行下载等操作的 goroutine。我将我的代码精简为一个最小的例子,以某种方式/希望仍然显示我的意图。当我运行它时,会有两个控制器,但不知何故,当它们触发 doStuffThatMayTakeLongTime() 方法时,它们都引用了相同的配置。那么,我是如何在这里混淆 goroutines 中变量和指针的作用域的呢?我对 Go 很陌生,这也是我第一次尝试使用使用指针的语言。嗯,我害羞的 C/C++ 尝试是在十多年前......所以我认为我的困惑在于引用/值/取消引用,但我看不到它。这是代码:package mainimport (        "log"        "time")type OriginContext struct {        Origin   string        Offset   time.Duration        Interval time.Duration}type Controller struct {        originContext *OriginContext}func NewController(originContext *OriginContext) (w *Controller) {        log.Printf("Controller starting loop for origin %s.", originContext.Origin)        w = &Controller{originContext}        w.start()        return w}func (w *Controller) start() {        log.Println("start() of", w.originContext.Origin)        go func() {                time.Sleep(w.originContext.Offset)                ticker := time.NewTicker(w.originContext.Interval)                go w.doStuffThatMayTakeLongTime() // iteration zero                for {                        select {                        case <-ticker.C:                                go w.doStuffThatMayTakeLongTime()                        }                }        }()}func (w *Controller) doStuffThatMayTakeLongTime() {        log.Printf("%s doing stuff", w.originContext.Origin)}这是一些输出:2015/09/07 14:30:11 Starting Controller alpha.2015/09/07 14:30:11 Controller starting loop for origin alpha.2015/09/07 14:30:11 start() of alpha2015/09/07 14:30:11 Starting Controller bravo.2015/09/07 14:30:11 Controller starting loop for origin bravo.2015/09/07 14:30:11 start() of bravo2015/09/07 14:30:16 bravo doing stuff2015/09/07 14:30:16 bravo doing stuff2015/09/07 14:30:26 bravo doing stuff2015/09/07 14:30:26 bravo doing stuff应该有 alpha 和 bravo做事,但只有 bravo。
查看完整描述

1 回答

?
12345678_0001

TA贡献1802条经验 获得超5个赞

问题出在这些行上:


    for _, ctx := range contexts {

            log.Printf("Starting Controller %s.", ctx.Origin)

            _ = NewController(&ctx)

    }

如语言规范中所述,该变量ctx在循环的每次迭代中重复使用。在循环的每次迭代中传递这个单个变量的地址。程序打印存储在此变量中的最后一个值(虽然不能保证,但变量存在竞争)。NewController


run example that prints &ctx


有几种方法可以解决这个问题。一种方法是将代码更改为:


for i := range contexts {

        log.Printf("Starting Controller %s.", context[i].Origin)

        _ = NewController(&context[i])

}

run it on the playground


通过此更改,NewController 被传递了一个指向切片元素的指针,而不是函数中指向变量的指针。


另一种选择是在循环体内声明一个新变量:


    for _, ctx := range contexts {

            ctx := ctx // <-- add this line

            log.Printf("Starting Controller %s.", ctx.Origin)

            _ = NewController(&ctx)

    }

run it on the playground


此选项在循环的每次迭代中分配一个 ctx,而第一个选项则没有。


查看完整回答
反对 回复 2021-11-08
  • 1 回答
  • 0 关注
  • 147 浏览
慕课专栏
更多

添加回答

举报

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