Go言語 | goroutine で slice を安全に扱う

goroutineによる非同期処理で、

- 100要素の配列を作る
- 配列の中身を1個ずつスライスに追加していく (元の配列と、中身の同じスライスが生成されるはず)
- スライスの要素数と中身を出力する

というプログラムを作ってみる。

## 最初のコード

```go
var wg sync.WaitGroup
var numbers = [100]int{} // 100要素ある配列
copiedNumbers := []int{} // 空のスライス

for i := range numbers {
	wg.Add(1) // 非同期処理のカウンタをインクリメント
	go func() { // 非同期処理の記述
		copiedNumbers = append(copiedNumbers, i)
		wg.Done() // 非同期処理のカウンタをデクリメント
	}()
}

wg.Wait() // 非同期処理が終わるのを待つ

fmt.Println(len(copiedNumbers)) // スライスの要素数を出力
fmt.Println(copiedNumbers) // スライスの中身を出力
```

## 結果

- スライスの要素数が100個に満たない
- プログラムを走らせるたび、気まぐれに要素数は変動する
- 重複した値も含まれる

```
86
[4 6 6 12 12 13 19 21 21 28 28 28 28 28 28 28 28 29 34 34 35 35 36 36 37 38 39 40 41 42 43 44 44 45 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 63 63 77 77 77 77 77 77 77 77 77 77 77 77 77 77 89 90 91 91 91 91 91 91 91 91 91 92 93 93 93 93 99 99 99 99 99]
```

## go func に値を渡すようにする

```diff
var wg sync.WaitGroup
var numbers = [100]int{}
copiedNumbers := []int{}

for i := range numbers {
	wg.Add(1)
-	go func() {
+	go func(i int) {
		copiedNumbers = append(copiedNumbers, i)
		wg.Done()
-	}(i)
+	}(i)
}

wg.Wait()

fmt.Println(len(copiedNumbers))
fmt.Println(copiedNumbers)
```

## 結果

- スライスの要素数は100個に満たないまま
- 値の重複はなくなった

```
78
[1 0 2 4 8 6 7 9 10 11 16 12 13 14 15 19 17 99 27 28 29 30 31 32 33 34 35 36 37 69 68 71 72 73 38 74 39 75 40 76 41 77 78 42 79 80 44 82 45 83 46 84 47 85 48 86 49 88 50 89 51 90 91 95 92 93 55 94 56 62 63 60 64 61 65 58 66 67]
```

## Mutexでロックをかける

```diff
+ var mutex = &sync.Mutex{}
var wg sync.WaitGroup
var numbers = [100]int{}
copiedNumbers := []int{}

for i := range numbers {
	wg.Add(1)
	go func(i int) {
+		mutex.Lock()
		copiedNumbers = append(copiedNumbers, i)
+		mutex.Unlock()
		wg.Done()
	}(i)
}

wg.Wait()

fmt.Println(len(copiedNumbers))
fmt.Println(copiedNumbers)
```

## 結果

- 要素数がちゃんと100個になった
- 値の重複がない
- 数字の出現順がランダムなので、非同期処理もできてるっぽい

```
100
[2 0 1 4 3 5 18 25 6 7 8 19 9 10 20 11 21 22 23 24 15 12 13 16 14 17 31 26 27 29 30 28 34 36 35 37 32 33 38 54 39 40 41 42 43 44 45 46 47 48 49 50 51 52 73 53 55 56 64 65 57 66 58 67 59 69 70 71 68 72 60 63 62 61 99 86 79 80 74 87 81 75 88 76 82 89 83 77 90 84 78 91 85 92 95 97 98 93 96 94]
```



# 検証用コード

```go
package main

import (
	"fmt"
	"sync"
)

func main() {
	copy_slice_without_pass_argument()
	copy_slice_without_lock()
	copy_slice_with_lock()
}

func copy_slice_without_pass_argument() {
	var wg sync.WaitGroup
	var numbers = [100]int{}
	copiedNumbers := []int{}

	for i := range numbers {
		wg.Add(1)
		go func() {
			copiedNumbers = append(copiedNumbers, i)
			wg.Done()
		}()
	}

	wg.Wait()

	fmt.Println(len(copiedNumbers))
	fmt.Println(copiedNumbers)
}

func copy_slice_without_lock() {
	var wg sync.WaitGroup
	var numbers = [100]int{}
	copiedNumbers := []int{}

	for i := range numbers {
		wg.Add(1)
		go func(i int) {
			copiedNumbers = append(copiedNumbers, i)
			wg.Done()
		}(i)
	}

	wg.Wait()

	fmt.Println(len(copiedNumbers))
	fmt.Println(copiedNumbers)
}

func copy_slice_with_lock() {
	var mutex = &sync.Mutex{}
	var wg sync.WaitGroup
	var numbers = [100]int{}
	copiedNumbers := []int{}

	for i := range numbers {
		wg.Add(1)
		go func(i int) {
			mutex.Lock()
			copiedNumbers = append(copiedNumbers, i)
			mutex.Unlock()
			wg.Done()
		}(i)
	}

	wg.Wait()

	fmt.Println(len(copiedNumbers))
	fmt.Println(copiedNumbers)
}

```


# 環境

- go version go1.8.1 darwin/amd64

# 参考

- [Concurrent map and slice types in Go – Marin Atanasov Nikolov – A place about Open Source Software, Operating Systems and some random thoughts](https://dnaeon.github.io/concurrent-maps-and-slices-in-go/)








<!-- Update From Qiita API -->

# チャットメンバー募集


何か質問、悩み事、相談などあればLINEオープンチャットもご利用ください。

https://line.me/ti/g2/eEPltQ6Tzh3pYAZV8JXKZqc7PJ6L0rpm573dcQ





# Twitter


https://twitter.com/YumaInaura


<!-- Update From Qiita API -->