donglingsai2880 2015-09-07 12:38
浏览 45
已采纳

我如何在这里混淆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.

  • 写回答

1条回答 默认 最新

  • duanbo1659 2015-09-07 13:33
    关注

    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).

    <kbd>run example that prints &ctx</kbd>

    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])
    }
    

    <kbd>run it on the playground</kbd>

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

    <kbd>run it on the playground</kbd>

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

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥20 有关区间dp的问题求解
  • ¥15 多电路系统共用电源的串扰问题
  • ¥15 slam rangenet++配置
  • ¥15 有没有研究水声通信方面的帮我改俩matlab代码
  • ¥15 对于相关问题的求解与代码
  • ¥15 ubuntu子系统密码忘记
  • ¥15 信号傅里叶变换在matlab上遇到的小问题请求帮助
  • ¥15 保护模式-系统加载-段寄存器
  • ¥15 电脑桌面设定一个区域禁止鼠标操作
  • ¥15 求NPF226060磁芯的详细资料