我如何在goroutine中混淆变量和指针的范围?

时间:2015-09-07 12:38:44

标签: pointers go concurrency scope

我正在学习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 做东西,但只有bravo。

1 个答案:

答案 0 :(得分:5)

问题在于以下几点:

    for _, ctx := range contexts {
            log.Printf("Starting Controller %s.", ctx.Origin)
            _ = NewController(&ctx)
    }

变量ctx在循环as described in the language specification的每次迭代中重用。 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将传递一个指向slice元素的指针,而不是指向函数中变量的指针。

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

    for _, ctx := range contexts {
            ctx := ctx // <-- add this line
            log.Printf("Starting Controller %s.", ctx.Origin)
            _ = NewController(&ctx)
    }

run it on the playground

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