2014-09-18 26 views
4

考慮以下Golang代碼(也對Go Playground):Golang爲什麼在門廊裏處理不同的封閉?

package main 

import "fmt" 
import "time" 

func main() { 
    for _, s := range []string{"foo", "bar"} { 
     x := s 
     func() { 
      fmt.Printf("s: %s\n", s) 
      fmt.Printf("x: %s\n", x) 
     }() 
    } 
    fmt.Println() 
    for _, s := range []string{"foo", "bar"} { 
     x := s 
     go func() { 
      fmt.Printf("s: %s\n", s) 
      fmt.Printf("x: %s\n", x) 
     }() 
    } 
    time.Sleep(time.Second) 
} 

此代碼產生以下輸出:

s: foo 
x: foo 
s: bar 
x: bar 

s: bar 
x: foo 
s: bar 
x: bar 

假設這是不是一些奇怪的編譯器缺陷,我很好奇爲什麼a)s的值在goroutine版本中被不同地解釋,然後在常規的func調用中和b)以及爲什麼將它分配給循環內部的局部變量。

+3

我挖@米切爾的'去func(s字符串){...}(s)'成語。解決這個問題的另一種方式是,Go範圍規則意味着* func在兩個*你的例子都訪問's'的當前值是什麼時候運行; goroutine只是在不同的時間運行。 – twotwotwo 2014-09-18 18:42:39

+2

使用賽跑檢測器運行此代碼應該會發現問題。 – 2014-09-19 00:01:21

回答

10

Go中的封閉詞在詞彙範圍內。這意味着封閉內從「外部」範圍內引用的任何變量都不是副本,而是實際上是一個引用。 A for循環實際上會多次重複使用同一個變量,因此您在介紹s變量的讀取/寫入之間引入競爭條件。

x正在分配一個新變量(與:=)並複製s,這導致每次都是正確的結果。

一般來說,最好的做法是傳遞任何你想要的參數,以便你沒有引用。例如:

for _, s := range []string{"foo", "bar"} { 
    x := s 
    go func(s string) { 
     fmt.Printf("s: %s\n", s) 
     fmt.Printf("x: %s\n", x) 
    }(s) 
} 
+0

謝謝米切爾。 @ericflo也指出了我在覆蓋它的文檔中的位置:http://golang.org/doc/faq#closures_and_goroutines – vishvananda 2014-09-18 17:58:50

+0

爲什麼在引用中傳遞的閉包中的變量而不是值(如其他情況夠程)? – Sinatra 2015-11-06 04:26:03

1

提示: 您可以使用「獲取地址操作符」 &確認變量是否是同一

讓我們稍微修改您的程序以幫助我們理解。

package main 

import "fmt" 
import "time" 

func main() { 
    for _, s := range []string{"foo", "bar"} { 
     x := s 
     fmt.Println(" &s =", &s, "\t&x =", &x) 
     func() { 
      fmt.Println("-", "&s =", &s, "\t&x =", &x) 
      fmt.Println("s =", s, ", x =", x) 
     }() 
    } 

    fmt.Println("\n\n") 

    for _, s := range []string{"foo", "bar"} { 
     x := s 
     fmt.Println(" &s =", &s, "\t&x =", &x) 
     go func() { 
      fmt.Println("-", "&s =", &s, "\t&x =", &x) 
      fmt.Println("s =", s, ", x =", x) 
     }() 
    } 
    time.Sleep(time.Second) 
} 

的輸出是:

&s = 0x1040a120 &x = 0x1040a128 
- &s = 0x1040a120 &x = 0x1040a128 
s = foo , x = foo 
    &s = 0x1040a120 &x = 0x1040a180 
- &s = 0x1040a120 &x = 0x1040a180 
s = bar , x = bar 



    &s = 0x1040a1d8 &x = 0x1040a1e0 
    &s = 0x1040a1d8 &x = 0x1040a1f8 
- &s = 0x1040a1d8 &x = 0x1040a1e0 
s = bar , x = foo 
- &s = 0x1040a1d8 &x = 0x1040a1f8 
s = bar , x = bar 

要點:

  • 在循環的每次迭代的變量s是相同的變量。
  • 在每次循環
  • 局部變量x是不同的變量,他們只是碰巧有相同的名稱x
  • 在第一個for循環中,func() {}()部分得到了在每個迭代執行,僅環繼續其在func() {}()完成後進行下一次迭代。
  • 在第二個循環(goroutine版本)中,go func() {}()語句本身即時完成。當func主體中的語句執行時由Go調度程序確定。但是當它們(func體中的語句)開始執行時,for循環已經完成!變量s是切片中的最後一個元素,即bar。這就是爲什麼我們在第二個循環輸出中有兩個「bar」。
相關問題