18
14

More than 1 year has passed since last update.

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

Last updated at Posted at 2017-04-30

goroutineによる非同期処理で、

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

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

最初のコード

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 に値を渡すようにする

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でロックをかける

+ 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]

検証用コード

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

参考

チャットメンバー募集

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

Twitter

18
14
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
18
14