Joh*_*nes 2 concurrency pointers scope go
我正在学习Go,编写一个简单的程序,同时从几个http服务器下载传感器数据文件.服务器上的传感器数据文件定期刷新(30秒或2分钟,取决于"原点").下载数据可能需要100毫秒到10秒.所以我读了每个服务器的一些配置(OriginContext).然后我为每个OriginContext启动一个控制器.每个控制器不断触发下载等的goroutine.
我把我的代码剥离到一个最小的例子,不知何故/希望仍然显示我的意图.当我运行它时,将有两个控制器,但不知何故,当它们触发doStuffThatMayTakeLongTime()方法时,它们都引用相同的配置.
那么,我如何在goroutine中混淆变量和指针的范围呢?
我是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)
}
func main() {
        contexts := []OriginContext{
                {
                        Origin:   "alpha",
                        Offset:   0 * time.Second,
                        Interval: 5 * time.Second,
                },
                {
                        Origin:   "bravo",
                        Offset:   5 * time.Second,
                        Interval: 10 * time.Second,
                },
        }
        for _, ctx := range contexts {
                log.Printf("Starting Controller %s.", ctx.Origin)
                _ = NewController(&ctx)
        }
        select {}
}
这是一些输出:
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 做的东西,但只有勇敢.
问题出在这些方面:
    for _, ctx := range contexts {
            log.Printf("Starting Controller %s.", ctx.Origin)
            _ = NewController(&ctx)
    }
如语言规范中所述,该变量ctx在循环的每次迭代中重用.在循环的每次迭代中传递此单个变量的地址.程序打印存储在此变量中的最后一个值(尽管不能保证,变量上存在竞争).NewController
有几种方法可以解决这个问题.一种方法是将代码更改为:
for i := range contexts {
        log.Printf("Starting Controller %s.", context[i].Origin)
        _ = NewController(&context[i])
}
通过此更改,NewController将传递一个指向slice元素的指针,而不是指向函数中变量的指针.
另一种选择是在循环体内声明一个新变量:
    for _, ctx := range contexts {
            ctx := ctx // <-- add this line
            log.Printf("Starting Controller %s.", ctx.Origin)
            _ = NewController(&ctx)
    }
此选项在循环的每次迭代中分配ctx,而第一个选项不循环.