隨著 Go 1.22 的發布,Go 團隊解決了長期存在的問題,這個問題與
for-range
迴圈變數的作用域有關,一直以來都是撰寫併發程式碼的開發人員常見的陷阱。具體來說,問題在於for-range
迴圈中的迴圈變數在每次迭代中都被重複使用,當涉及閉包時,就會導致意想不到的行為。
Go 1.22 之前的問題
考慮以下範例:
package forange
import "fmt"
func Do() {
done := make(chan bool)
values := []string{"a", "b", "c"}
for _, v := range values {
go func() {
fmt.Println(v) // 輸出非預期值
done <- true
}()
}
for _ = range values {
<-done
}
}
並且有一個go.mod檔案如下
module playground
go 1.16
require github.com/ozgio/strutil v0.4.0
在 Go 1.22 之前的版本中,執行此程式碼通常會導致多次列印相同的值(例如,c
),而不是按預期列印values
中的每個值(a
、b
、c
)。這是因為迴圈變數v
在每次迭代中都被重複使用,而 goroutines 擷取了相同的記憶體位址。
Go 1.22 的修正
Go 1.22 引進了對此行為的修正,方法是使迴圈變數的行為如同在每次迭代中都被重新宣告一樣。這表示每個閉包現在都應該正確地擷取其對應迭代的v
值。
但是,如果您已升級到 Go 1.22,但仍然觀察到舊的、有問題的行為,那麼罪魁禍首可能是您的go.mod
檔案。只有當go.mod
檔案指定 Go 版本為1.22
或更高版本時,此修正才會生效。此相依性是有意的,允許開發人員在需要時保留與舊有行為的向後相容性。
在您的go.mod
中指定go 1.22
並應用修正後,執行上述程式應該會正確列印 a、b、c。每個 goroutine 現在都會擷取其迭代的正確v
值。
經驗教訓
- 保持更新:始終閱讀新 Go 版本的發行說明。Go 1.22 發行說明特別提到此更改及其對
go.mod
的相依性。 - 使用正確的
go.mod
版本控制:許多 Go 語言功能都與go.mod
中的go
版本相關聯。如果您依賴新功能或修正,請確保指定正確的版本。 - 徹底測試:即使在升級 Go 之後,也請透過執行全面的測試來確保您的程式按預期執行。
為什麼需要此相依性?
go.mod
版本控制方法允許團隊逐步採用修正,而不會破壞現有的建置。透過將新的語言行為與go.mod
中的go
版本耦合,Go 團隊確保了向後相容性,同時也為開發人員提供了更順暢的遷移。
如果您遇到類似的挑戰,或對這如何影響您的工作流程有任何見解,請隨時在評論中分享!
Nice article?