Go言語学習その30~Goroutineその1

Go における並行処理を実現する Goroutine を見てみます。


■ Goroutine で関数を並行実行

Go の処理は関数単位で動くわけですが
関数は Go のランタイムが管理するスレッドである
Goroutine 上で動きます。

Go の処理は必ず main 関数から始まりますが
この main 関数も Goroutine 上で動いていて、
main 関数から呼ばれる関数もデフォルトでは
main 関数と同じ Goroutine 上で動きます。

まず、以下のサンプルを実行してみます。

package main

import (
	"fmt"
	"time"
)

func sampleProcess() {
	for i := 0; i < 5; i++ {
		fmt.Println("★sampleProcess", i)
		time.Sleep(10 * time.Millisecond)
	}
}

func main() {
	sampleProcess()

	for i := 0; i < 5; i++ {
		fmt.Println("★main", i)
		time.Sleep(20 * time.Millisecond)
	}
}

実行結果は以下になります。

★sampleProcess 0
★sampleProcess 1
★sampleProcess 2
★sampleProcess 3
★sampleProcess 4
★main 0
★main 1
★main 2
★main 3
★main 4

main 関数から sampleProcess 関数が実行され
sampleProcess 関数の処理が終わってから
main 関数の処理が実行されてますね。
main 関数が実行されている Goroutine 上で
sampleProcess 関数も動いています。

では、sampleProcess 関数を
main 関数とは別の Goroutine 上で動かすにはどうすればいいでしょう?
以下のように関数呼び出し時に関数名の前に「go」を記載すれば
別の Goroutine 上で関数が実行されるようになります。

go【別の Goroutine 上で実行したい関数名】

以下にサンプルを示します。

package main

import (
	"fmt"
	"time"
)

func sampleProcess() {
	for i := 0; i < 5; i++ {
		fmt.Println("★sampleProcess", i)
		time.Sleep(10 * time.Millisecond)
	}
}

func main() {
	go sampleProcess()

	for i := 0; i < 5; i++ {
		fmt.Println("★main", i)
		time.Sleep(20 * time.Millisecond)
	}
}

前述のサンプルとの違いは
sampleProcess 関数呼び出しの行の先頭に
「go」を付けているところですね。

go sampleProcess()

実行結果は以下になります。

★main 0
★sampleProcess 0
★sampleProcess 1
★main 1
★sampleProcess 2
★main 2
★sampleProcess 3
★sampleProcess 4
★main 3
★main 4

上記結果は、実行する度に出力する順番が変わりますが
結果を見てみると
sampleProcess 関数の処理の終了を待たずに main 関数の処理が動いている
ことがわかります。

関数呼び出し時に「go」を付けることで
go sampleProcess()
main 関数は「自分が動いているものとは別の Goroutine」に
sampleProcess 関数の実行を依頼し、
依頼が終わったら sampleProcess 関数の終了を待たずに
すぐに自分の処理に戻ります。

main 関数と sampleProcess 関数 の処理が並行実行されるわけです。

※ time.Sleepをなぜしてる?
  サンプルの for 文内で
  time.Sleep(10 * time.Millisecond)
  として処理を少し待機させてますが
  これは
  main 関数の処理が待機してるときに
  sampleProcess 関数の処理結果が表示されたり
  またその逆で
  sampleProcess 関数の処理が待機してるときに
  main 関数の処理結果が表示されたり
  するようにすることで
  並行実行されてるのを確認しやすくするためです。

■ main の Goroutine が終わったら全てが終わる

前述のサンプルから「time.Sleep~」を削除した
以下のサンプルを実行してみます。

package main

import (
	"fmt"
)

func sampleProcess() {
	for i := 0; i < 5; i++ {
		fmt.Println("★sampleProcess", i)
	}
}

func main() {
	go sampleProcess()

	for i := 0; i < 5; i++ {
		fmt.Println("★main", i)
	}
}

実行結果は以下になります。

★main 0
★main 1
★main 2
★main 3
★main 4
★sampleProcess 0
★sampleProcess 1
★sampleProcess 2

この結果も実行する度に出力内容が変わる可能性がありますが
結果は以下のようになっています。
・main 関数の処理は for ループ5回分全て出力されている
・sampleProcess 関数の処理は for ループ回数が3回で止まってる

main 関数は
go sampleProcess()
で別の Goroutine で sampleProcess が実行されるように依頼した後
すぐに自分の処理に入り、
for ループ5回分の処理を実行します。
time.Sleep による待機が無いため実行はすぐに終わります。

sampleProcess 関数は
別の Goroutine 生成 ⇒ 関数処理開始
といった感じで「Goroutine 生成」が入る分
main 関数よりも開始が遅れるのですが
「for ループ回数が3回で止まってる」
のは
「main 関数の Goroutine が終わったため」
になります。

main 関数の Goroutine が終了してしまったら
main 関数から別の Goroutine で起動された関数の処理は
強制終了してしまうんですね。

なので、別の Goroutine で起動された関数の処理が終了するまで
main 関数の処理を何らかの方法で待機する必要がありますが、
これについては次回の記事で見てみようと思います。

※ main 関数の time.Sleep の待機時間を長くしてる理由
  前述「■ Goroutine で関数を並行実行」でのサンプルでは
  sampleProcess 関数側の time.Sleep は
  time.Sleep(10 * time.Millisecond)
  main 関数側の time.Sleep は
  time.Sleep(20 * time.Millisecond)
  と、main 関数側の time.Sleep の待機時間を少し長くしてますが
  これは main 関数が先に終わらないようにするためです。

次回も Goroutine の話になります。

#プログラミング
#IT
#プログラミング言語
#Go言語
#GO
#Golang

この記事が気に入ったらサポートをしてみませんか?