Skip to Content

はじめてのGoroutine

概要

簡単な並行処理をやってみた

結論

手を動かしてトラブってみないとわからないことが多い
私のような初心者はぜひ手を動かしてほしい

実践

forで1回毎に1秒待期してカウントを表示
goroutineのありなしで実行時間を計測

  • 1回目(forのパターン)はただのforで順次処理
    • 5回ループするため5秒かかる見込み
  • 2回目(goroutineのパターン)はgoroutineありで並行処理
    • 5回ループするけど並行処理だから早くなる見込み

テストコード

実行時間を計測するため、start / endの変数が入り見通しが悪くなってしまった

forのパターン

forsampleForを5回まわす処理
sampleFor関数はiの数の表示をし、1秒待期する

goroutineのパターン

forsampleGoroutineを5回まわす処理ではあるが、
go sampleGroutineと先頭にgoをつけてgoroutineを使用する
sampleGroutine関数はiの数の表示をし、1秒待期する

goroutineの説明は後述

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
  // forのパターン
	startFor := time.Now()
	for i := 0; i < 5; i++ {
		sampleFor(i)
	}
	endFor := time.Now()
	fmt.Printf("for実行時間: %f秒\n", (endFor.Sub(startFor)).Seconds())

  // goroutineのパターン
	var wg sync.WaitGroup
	startGoroutine := time.Now()
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go sampleGoroutine(i, &wg)
	}
	wg.Wait()
	endGotoutine := time.Now()
	fmt.Printf("goroutine有り実行時間: %f秒\n", (endGotoutine.Sub(startGoroutine)).Seconds())
}

func sampleFor(i int) {
	fmt.Println(i)
	time.Sleep(time.Second) // 1秒待期
}

func sampleGoroutine(i int, wg *sync.WaitGroup) {
	defer wg.Done()
	fmt.Println(i)
	time.Sleep(time.Second) // 1秒待期
}

実行結果

forのパターン

想定通り1秒待期×5回で5秒弱の実行時間
順次処理のため0から4まで順番通りに実行されている

goroutineのパターン

1秒弱で完了している
順不同で実行されるため0から4の表示順はバラバラ
何回か実行していれば綺麗に揃う場合ももちろんあるとは思う

TOPIC

初めてgorutineを使って得た知見を羅列

並行なの並列なの?

goroutineは並 処理
図を見た方が理解しやすい
イメージはこちら

Syncパッケージ

関数の先頭にgoをつけて実行すると並行処理で走る
しかし、今回はsampleGortoutine関数が終了する前に、
main関数が先に終了してしまいプログラム自体が終わってしまう
要は並行処理の部分が実行されないで終わってしまった

sync.WaitGroupを利用してsampleGotoutine関数の処理が終わるまで待つように書くのが一般的のようだ

goroutineの実行と、goroutineにおけるsync.WaitGroupの役割

  • wg.Add(1) でwgをインクリメント
  • wg.Done() でwgをデクリメント
  • wg.Wait() 完了を待つ

今回のポイント

main関数でwg.Wait()を書いてgoroutineの完了を待つ
forの中でwg.Add(1)と書いて1個処理があることを示してからsampleGorutine関数を実行
sampleGorutineの中で処理が終わったらwg.Done()でデクリメントする
wgはsampleGorutine内でデクリメントするため、ポインタ型で関数に渡す

sync.WaitGroupの正しい使い方

感想

並行処理自体が初めてだったから新しい学びがたくさんあった
オライリーの並行処理本読みたいけどもう少し勉強してからでないと積んで終わりそうだ
そのくらい学ばないといけないことが多い
手を動かしていくしかないかな、実践あるのみ

参考

https://kwmt27.net/index.php/2012/07/06/golang%E3%81%A7%E3%81%8B%E3%81%8B%E3%81%A3%E3%81%9F%E5%87%A6%E7%90%86%E6%99%82%E9%96%93%E3%82%92%E8%A8%88%E7%AE%97%E3%81%99%E3%82%8B%E3%81%AB%E3%81%AF/
https://medium.com/eureka-engineering/go-waitgroup-map-data-race-ec3561de6e47
https://qiita.com/yoshinori_hisakawa/items/486752636cf66225483a