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

I am learning Go by writing a simple program that concurrently downloads sensor data files from a few http servers. The sensor data files on the servers are refreshed at regular intervals (30 seconds or 2 minutes, depends on the 'origin'). Downloading the data can take from 100ms to 10 seconds. So I read some configurations for each server (OriginContext). Then I start a controller for each OriginContext. Each controller continuously fires a goroutine that does the download etc.

I stripped my code down to a minimal example that somehow/hopefully still shows my intentions. When I run it, there will be two controllers, but somehow when they fire the doStuffThatMayTakeLongTime() methods they all refer to the identical configuration.

So, how did I confuse scopes of variables and pointers in goroutines here?

I am very new to Go and also this is the first time I try to use a language that uses pointers. Well, my shy C/C++ attempts are more than a decade ago... so I assume my confusion is with reference/value/dereference, but I can't see it.

This is the code:

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 {}
}

And this is some output:

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

There should be alpha and bravo doing stuff, but there is just bravo.

The problem is on these lines:

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

The variable ctx is reused on every iteration of the loop as described in the language specification. NewController is passed address of this single variable on every iteration of loop. The program prints the last values stored in this variable (although that's not guaranteed, there's a race on the variable).

run example that prints &ctx

There are a few ways to fix this. One way is to change the code to:

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

run it on the playground

With this change, NewController is passed a pointer to the slice element instead of a pointer to variable in the function.

Another option is to declare a new variable inside the body of the loop:

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

run it on the playground

This option allocates a ctx on every iteration through the loop while the first option does not.