参考
こちらの動画 (Concurrency Made Easy (Practical Tips For Effective Concurrency In Go) を参考にしています。
- コードを少し変えています。
問題
下記コードで出力される結果は何でしょうか?
package main
import (
"fmt"
"sync"
)
func main() {
repos := []string{"aaa", "bbb", "ccc", "ddd"}
sem := make(chan int, 4)
var wg sync.WaitGroup
wg.Add(len(repos))
for _, repo := range repos {
go func() {
defer wg.Done()
sem <- 1
fmt.Println(repo)
<-sem
}()
}
wg.Wait()
close(sem)
}
答えは、
ddd
ddd
ddd
ddd
です。
原因
スコープが "今" の range
値 となっているから。
for ループが最速すぎて、結果的に、全ての Goroutine が for-range の最後の "ddd" を取得することになっている。
(注意: for-range
で返ってくる値は、配列の順番通りであることは保証していないので、環境によっては変わる可能性あり)
改善方法
無名関数の引数に Goroutine を 実行するタイミングの range
値 を渡す。
package main
import (
"fmt"
"sync"
)
func main() {
repos := []string{"aaa", "bbb", "ccc", "ddd"}
sem := make(chan int, 4)
var wg sync.WaitGroup
wg.Add(len(repos))
for i := range repos {
go func(rep string) {
defer wg.Done()
sem <- 1
fmt.Println(rep)
<-sem
}(repos[i])
}
wg.Wait()
close(sem)
}
For-range ループ利用時の注意
for-range
では 取り出す順番は保証されていません。
また、Goroutine も処理が終了したタイミングで戻ってくるので、意図した通りの順番にはならないケースが多いです。
ちなみに改善後のコード実行結果は、下記のようになりました。
bbb
aaa
ccc
ddd