2013-06-21 86 views
3

我看Golang文檔中的typical data races,我不明白爲什麼有這個程序有問題:爲什麼在這個程序中有競爭條件?

func main() { 
    var wg sync.WaitGroup 
    wg.Add(5) 
    for i := 0; i < 5; i++ { 
     go func() { 
      fmt.Println(i) // Not the 'i' you are looking for. 
      wg.Done() 
     }() 
    } 
    wg.Wait() 
} 

它打印5, 5, 5, 5, 5時候我會想到它打印0, 1, 2, 3, 4(不必須按此順序)。

我看到它的方式,當循環內部創建goroutine時,i的值是已知的(例如,可以在循環開始時執行log.Println(i)並查看期望值)。所以我希望goroutine能夠在創建時獲得i的值,並在以後使用它。

顯然這不是發生了什麼,但爲什麼?

回答

7

您的函數字面引用了外部作用域中的i。如果您要求價值i,您將得到i現在的價值。爲了使用在轉到例程的創建時間的i值,提供一個參數:

func main() { 
    var wg sync.WaitGroup 
    wg.Add(5) 
    for i := 0; i < 5; i++ { 
     go func(i int) { 
      fmt.Println(i) 
      wg.Done() 
     }(i) 
    } 
    wg.Wait() 
} 

runnable example

2

可變i未在函數文本中聲明,所以它成爲的一部分關閉。如何理解閉包的一個簡單方法是思考如何實施閉包。簡單的解決方案是使用指針。你可以認爲文字的功能是由編譯器改寫成一些

func f123(i *int) { 
     fmt.Println(*i) 
     wg.Done    
} 
  • 在該功能的調用,通過旅途中的說法,i變量的地址被傳遞到所謂的F123(例如名稱由編譯器生成)。

  • 您可能使用默認的GOMAXPROCS == 1,所以for循環執行5次而沒有任何調度,因爲循環沒有I/O或其他「調度點」,例如通道操作。

  • 當循環終止時,i == 5wg.Wait最終觸發執行五個準備運行的例程(用於f123)。它們當然都有相同的指針指向相同的整數變量i

  • 每一個夠程現在看到的5

相同i值與GOMAXPROCS> 1時,或者當循環率控制在運行時,您可能會得到不同的輸出。這也可以通過例如runtime.Gosched來完成。

0

正如其他人所提到的,您的變量i在您創建的goroutine中使用,但是這些goroutine可以在未來循環執行,一旦循環已完成循環。在這一點上,i的值不是5,並且所有的例行程序都被啓動,讀取值爲i(作爲5)並繼續他們的快樂方式。

我相信FUZxxl提到使用傳遞值i作爲函數的參數。我認爲對於相當複雜的系統來說這是一個好主意,特別是如果您踢出去的例程不是內聯關閉。然而,在大多數情況下,我認爲這是一個很大清潔劑只需要創建一個新的臨時變量對每個去例行:

http://play.golang.org/p/6dnkrEGfhn

func main() { 
    var wg sync.WaitGroup 
    wg.Add(5) 
    for i := 0; i < 5; i++ { 
     myi := i 
     go func() { 
      fmt.Println(myi) 
      wg.Done() 
     }() 
    } 
    wg.Wait() 
} 

的效果是一樣的,它可以說,這是一個偏好問題,它是。這是我的偏好:p