0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【よくある間違いを避ける】For-range ループで意図した値を Goroutine に渡す

Last updated at Posted at 2021-02-28

参考

こちらの動画 (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
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?