結論:
閉包函數可以直接引用外層代碼定義的變量,
但是,注意,閉包函數里面引用的是變量的地址,
當goroutine被調度時,改地址的值才會被傳遞給goroutine 函數。
介紹
go的閉包是一個很有用的東西。但是如果你不了解閉包是如何工作的,那么他也會給你帶來一堆的bug。這里我會拿出Go In Action這本書的一部分代碼,來說一說在使用閉包的時候可能遇到的坑。全部的代碼在github上。
閉包的坑
首先看一段代碼:
search/search.go 29 // Launch a goroutine for each feed to find the results. 30 for _, feed := range feeds { 31 // Retrieve a matcher for the search. 32 matcher, exists := matchers[feed.Type] 33 if !exists { 34 matcher = matchers["default"] 35 } 36 37 // Launch the goroutine to perform the search. 38 go func(matcher Matcher, feed *Feed) { 39 Match(matcher, feed, searchTerm, results) 40 waitGroup.Done() 41 }(matcher, feed) 42 }
這段代碼從30行開始遍歷一個Feed的slice。在for range語句中聲明的feed變量的值在每一個循環中都不同。之后從32行的代碼在檢查一個某個特定的key值是否有值,如果不存在則賦一個默認值。和feed變量一樣,matcher的值也是每個循環都不一樣。
現在我們可以跳到38行到41行。這幾行代碼顯然還是在for range循環中的。這里我們定義了一個匿名函數,並把這個函數做為一個goroutine運行。這個匿名函數接受兩個參數,第一個是Matcher類型的值,第二個是一個Feed類型的指針。在地41行,我們可以蛋刀matcher和feed兩個變量被傳入了匿名函數中。
這個匿名函數在第39行的實現很有意思。這里我們可以看到一個對Match方法的調用。這個方法接受4個參數,如果你仔細看的話,前兩個參數就是我們定義匿名函數聲明的而兩個參數。后面的兩個我們沒有在匿名函數中聲明。而是作為變量直接在匿名函數使用了。
search/search.go 37 // Launch the goroutine to perform the search. 38 go func(matcher Matcher, feed *Feed) { 39 Match(matcher, feed, searchTerm, results) 40 waitGroup.Done() 41 }(matcher, feed) 42 }
變量searchTerm和results是定義在閉包外部的。我們可以在匿名函數內部直接使用,而不必作為參數傳入后再使用。這里就會有一個問題:我們為什么要把變量matcher和feed作為參數傳入而其他的兩個不是呢?
我在一開始就指出,matcher和feed兩個變量的值是如何在每一個for range循環中改變的。searchTerm和results的值不會隨着循環而改變,他們的值在每一個goroutine的生命周期中都是常量。當然,這個goroutine就是使用的匿名函數。那么,為什么要這么做呢?
當我們在匿名函數閉包中使用一個變量的時候,我們不必在匿名函數聲明的時候作為參數傳遞。這個匿名函數閉包可以直接訪問到定義在其外部的變量,也就是說對這個變量的修改會在匿名函數閉包內部體現出來,也就是這里的goroutine。如果我們把matcher和feed變量這樣使用,而不是把他們作為參數傳入匿名函數閉包。那么多數情況下gotoutine只會處理for range循環的最后一個值。
在這個例子中,所有的goroutine都會並發執行。for range循環也許在第一個最多第二個goroutine還在運行的時候就運行完了,matcher和feed變量只會有最后一次循環時候的值。也就是說即使不是全部的goroutine也是大部分的goroutine會處理這些變量的相同的值。這種情況適用於searchTerm和results變量,因為他們不會在循環中改變值。
結論
幸好我們可以聲明可以接收參數的匿名函數,這些類型的閉包問題也就引刃而解。在我們上面的例子中,當每一個匿名函數都聲明在for range的作用域內的時候,matcher和feed變量的值在作為參數傳入匿名函數閉包的時候也就同時被鎖定。在使用閉包訪問外部變量的時候,問問你自己這個變量時候會發生改變,這樣的改變對閉包的運行有什么影響。
參考資料:
http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html#closure_for_it_vars
http://studygolang.com/articles/7994
http://studygolang.com/articles/4738
http://blog.csdn.net/htyu_0203_39/article/details/50985187
http://studygolang.com/articles/356
http://www.jb51.net/article/74166.htm
http://www.cnblogs.com/yjf512/archive/2012/12/09/2810313.html
https://www.zhihu.com/question/49341044?from=profile_question_card
http://www.jb51.net/article/81813.htm