這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
本文即Go語言的那些坑三。
不要對Go並發函數的執行時機做任何假設
請看下列的列子:
12345678910111213141516 |
import ("fmt""runtime""time")func main(){names := []string{"lily", "yoyo", "cersei", "rose", "annei"}for _, name := range names{go func(){fmt.Println(name)}()}runtime.GOMAXPROCS(1)runtime.Gosched()} |
請問輸出什嗎?
答案:
12345 |
anneianneianneianneiannei |
為什麼呢?是不是有點詫異?
輸出的都是“annei”,而“annei”又是“names”的最後一個元素,那麼也就是說程式列印出了最後一個元素的值,而name對於匿名函數來講又是一個外部的值。因此,我們可以做一個推斷:雖然每次迴圈都啟用了一個協程,但是這些協程都是引用了外部的變數,當協程建立完畢,再執行列印動作的時候,name的值已經不知道變為啥了,因為主函數協程也在跑,大家並行,但是在此由於names數組長度太小,當協程建立完畢後,主函數迴圈早已結束,所以,列印出來的都是遍曆的names最後的那一個元素“annei”。
如何證實以上的推斷呢?
其實很簡單,每次迴圈結束後,停頓一段時間,等待協程列印當前的name便可。
1234567891011121314151617 |
import ("fmt""runtime""time")func main(){names := []string{"lily", "yoyo", "cersei", "rose", "annei"}for _, name := range names{go func(){fmt.Println(name)}()time.Sleep(time.Second)}runtime.GOMAXPROCS(1)runtime.Gosched()} |
列印結果:
12345 |
lilyyoyocerseiroseannei |
以上我們得出一個結論,不要對“go函數”的執行時機做任何的假設,除非你確實能做出讓這種假設成為絕對事實的保證。
假設T類型的方法上接收器既有T類型的,又有*T指標類型的,那麼就不可以在不能定址的T值上調用*T接收器的方法
請看代碼,試問能正常編譯通過嗎?
1234567891011121314151617181920 |
import ("fmt")type Lili struct{Name string}func (Lili *Lili) fmtPointer(){fmt.Println("poniter")}func (Lili Lili) fmtReference(){fmt.Println("reference")}func main(){li := Lili{}li.fmtPointer()} |
答案:
感覺有點詫異,請接著看以下的代碼,試問能編譯通過?
12345678910111213141516171819 |
import ("fmt")type Lili struct{Name string}func (Lili *Lili) fmtPointer(){fmt.Println("poniter")}func (Lili Lili) fmtReference(){fmt.Println("reference")}func main(){Lili{}.fmtPointer()} |
答案:
123 |
不能編譯通過。“cannot call pointer method on Lili literal”“cannot take the address of Lili literal” |
是不是有點奇怪?這是為什麼呢?其實在第一個程式碼範例中,main主函數中的“li”是一個變數,li的雖然是類型Lili,但是li是可以定址的,&li的類型是Lili,因此可以調用Lili的方法。