## 事象
[[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/)