## 事象 [[Go]]で以下のコードを書いて実行する。 ```go package main import ( "log" "time" ) func main() { values := []string{"one", "two", "three", "four", "five", "six"} sem := make(chan struct{}, 3) results := make(chan string, len(values)) for i, v := range values { sem <- struct{}{} go func() { log.Printf("Do task [%d]\n", i+1) time.Sleep(3 * time.Second) log.Printf("Result task [%d]: %s\n", i+1, v) results <- v <-sem }() } for i := 0; i < cap(results); i++ { <-results } } ``` 結果が以下のようになる。 ```console 2023/08/19 17:42:22 Do task [4] 2023/08/19 17:42:22 Do task [4] 2023/08/19 17:42:22 Do task [4] 2023/08/19 17:42:25 Result task [4]: four 2023/08/19 17:42:25 Do task [5] 2023/08/19 17:42:25 Result task [4]: four 2023/08/19 17:42:25 Do task [6] 2023/08/19 17:42:25 Result task [4]: four 2023/08/19 17:42:25 Do task [6] 2023/08/19 17:42:28 Result task [6]: six 2023/08/19 17:42:28 Result task [6]: six 2023/08/19 17:42:28 Result task [6]: six ``` 期待結果の一例は以下のような感じ。これと異なる。 ```console ```console 2023/08/19 16:53:01 Do task [1] 2023/08/19 16:53:01 Do task [2] 2023/08/19 16:53:01 Do task [3] 2023/08/19 16:53:04 Result task [1]: one 2023/08/19 16:53:04 Result task [2]: two 2023/08/19 16:53:04 Do task [4] 2023/08/19 16:53:04 Result task [3]: three 2023/08/19 16:53:04 Do task [5] 2023/08/19 16:53:04 Do task [6] 2023/08/19 16:53:07 Result task [4]: four 2023/08/19 16:53:07 Result task [5]: five 2023/08/19 16:53:07 Result task [6]: six ``` バージョンは[[Go 1.21]]。 ## 原因 [[Go 1.21]]とそれ以前のバージョンでは、イテレーターの変数(下記だと `i` と `v`)は同じ変数が繰り返し利用されるため、[[クロージャ (Go)|クロージャ]]や[[ゴルーチン]]が実行時に参照する変数がその時点で値となっているため。 ```go for i, v := range values { sem <- struct{}{} go func() { log.Printf("Do task [%d]\n", i+1) time.Sleep(3 * time.Second) log.Printf("Result task [%d]: %s\n", i+1, v) results <- v <-sem }() } ``` ## 解決方法 ## [[Go 1.20]]以下の場合 for文の中で、[[ゴルーチン]]を呼び出す前にループ内スコープの変数へキャプチャする。これは同名でも良い。 ```go for i, v := range values { // キャプチャ i := i v := v // --- sem <- struct{}{} go func() { log.Printf("Do task [%d]\n", i+1) time.Sleep(3 * time.Second) log.Printf("Result task [%d]: %s\n", i+1, v) results <- v <-sem }() } ``` ## [[Go 1.21]]の場合 先の方法に加えて、環境変数`GOEXPERIMENT=loopvar`を設定して`go`コマンドを実行することにより問題を回避できる。 詳細は以下を参照。 <div class="link-card"> <div class="link-card-header"> <img src="https://github.githubassets.com/favicons/favicon.svg" class="link-card-site-icon"/> <span class="link-card-site-name">GitHub</span> </div> <div class="link-card-body"> <div class="link-card-content"> <div> <p class="link-card-title">LoopvarExperiment</p> </div> <div class="link-card-description"> The Go programming language. Contribute to golang/go development by creating an account on GitHub. </div> </div> <img src="https://opengraph.githubassets.com/79ccccd2ef0065345d072a89b8e6ecc4b7fa2954882ebe84fcac7587a2ab93ff/golang/go" class="link-card-image" /> </div> <a href="https://github.com/golang/go/wiki/LoopvarExperiment"></a> </div> ## 参考 - [Frequently Asked Questions \(FAQ\) \- The Go Programming Language](https://go.dev/doc/faq#closures_and_goroutines) - [LoopvarExperiment · golang/go Wiki](https://github.com/golang/go/wiki/LoopvarExperiment) - [Go 1\.22で導入されるforループ変数の変更](https://shogo82148.github.io/blog/2023/06/22/2023-06-22-go-loopvar-experiment/)