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

我正在通过编写一个简单的程序来学习 Go,该程序同时从几个 http 服务器下载传感器数据文件。服务器上的传感器数据文件会定期刷新(30 秒或 2 分钟,取决于“来源”)。下载数据可能需要 100 毫秒到 10 秒的时间。所以我阅读了每个服务器(OriginContext)的一些配置。然后我为每个 OriginContext 启动一个控制器。每个控制器持续触发一个执行下载等操作的 goroutine。


我将我的代码精简为一个最小的例子,以某种方式/希望仍然显示我的意图。当我运行它时,会有两个控制器,但不知何故,当它们触发 doStuffThatMayTakeLongTime() 方法时,它们都引用了相同的配置。


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


我对 Go 很陌生,这也是我第一次尝试使用使用指针的语言。嗯,我害羞的 C/C++ 尝试是在十多年前......所以我认为我的困惑在于引用/值/取消引用,但我看不到它。


这是代码:


package main


import (

        "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 alpha

2015/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 bravo

2015/09/07 14:30:16 bravo doing stuff

2015/09/07 14:30:16 bravo doing stuff

2015/09/07 14:30:26 bravo doing stuff

2015/09/07 14:30:26 bravo doing stuff

应该有 alpha 和 bravo做事,但只有 bravo。


ABOUTYOU
浏览 148回答 1
1回答

12345678_0001

问题出在这些行上:&nbsp; &nbsp; for _, ctx := range contexts {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log.Printf("Starting Controller %s.", ctx.Origin)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _ = NewController(&ctx)&nbsp; &nbsp; }如语言规范中所述,该变量ctx在循环的每次迭代中重复使用。在循环的每次迭代中传递这个单个变量的地址。程序打印存储在此变量中的最后一个值(虽然不能保证,但变量存在竞争)。NewControllerrun example that prints &ctx有几种方法可以解决这个问题。一种方法是将代码更改为:for i := range contexts {&nbsp; &nbsp; &nbsp; &nbsp; log.Printf("Starting Controller %s.", context[i].Origin)&nbsp; &nbsp; &nbsp; &nbsp; _ = NewController(&context[i])}run it on the playground通过此更改,NewController 被传递了一个指向切片元素的指针,而不是函数中指向变量的指针。另一种选择是在循环体内声明一个新变量:&nbsp; &nbsp; for _, ctx := range contexts {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ctx := ctx // <-- add this line&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log.Printf("Starting Controller %s.", ctx.Origin)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _ = NewController(&ctx)&nbsp; &nbsp; }run it on the playground此选项在循环的每次迭代中分配一个 ctx,而第一个选项则没有。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go