2015-09-07 86 views
2

我正在學習Go通過編寫一個簡單的程序,同時從幾個http服務器下載傳感器數據文件。服務器上的傳感器數據文件會定期刷新(30秒或2分鐘,取決於「原點」)。下載數據可能需要100ms到10秒。所以我讀了每個服務器的一些配置(OriginContext)。然後我爲每個OriginContext啓動一個控制器。每個控制器不斷地啓動下載等的goroutine。我如何在這裏混淆了goroutines中變量的範圍和指針?

我把我的代碼剝離到一個最小的例子,希望仍然顯示我的意圖。當我運行它時,會有兩個控制器,但不知何故,當他們啓動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) 
} 

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 做的東西,但只是喝彩。

+1

嘗試記錄:''NewController'函數開始處的'log.Printf(「控制器以地址%v處的上下文開始,originContext)''。你應該看到兩次相同的地址。 – LeGEC

回答

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傳遞指針,到切片元件,而不是在功能的指針的變量。

另一種選擇是申報循環體中一個新的變量:

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

run it on the playground

此選項通過循環而第一選項不分配在每個迭代CTX 。