こんにちは。KOUKIです。
Go言語のGoroutineは軽量のスレッドで、並列処理プログラミングで大活躍します。
Go言語を使うメリットの一つであり、使いこなすことができれば、かなり強力な武器になります。
本記事では、Goroutineの使い方を紹介します。
<目次>
Goroutineとgoキーワード
Go言語で並列処理を実行したい場合は、「go」キーワードと共に並列化したい関数を呼び出します。
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 |
package main import ( "fmt" "time" ) func noGoroutine() { for i := 0; i < 5; i++ { time.Sleep(100 * time.Millisecond) fmt.Println("No goroutine") } } func goroutine() { for i := 0; i < 5; i++ { time.Sleep(100 * time.Millisecond) fmt.Println("Yes goroutine") } } func main() { go goroutine() noGoroutine() } |
goroutine関数は、goキーワードと共に呼び出しているので、並列で実行されます。
一方で、noGoroutineは、普通に呼び出しているので、並列化されません。
このプログラムを実行します。
1 2 3 4 5 6 7 8 9 10 11 |
$ go run main.go No goroutine Yes goroutine No goroutine Yes goroutine Yes goroutine No goroutine No goroutine Yes goroutine Yes goroutine No goroutine |
わかりずらいかもしれませんが、「Yes goroutine」は並列で呼び出された結果です。
試しに「go」キーワードを取り除いて実行してみましょう。
1 2 3 4 |
func main() { goroutine() // goキーワードを取り除く noGoroutine() } |
1 2 3 4 5 6 7 8 9 10 11 |
$ go run main.go Yes goroutine Yes goroutine Yes goroutine Yes goroutine Yes goroutine No goroutine No goroutine No goroutine No goroutine No goroutine |
この場合は、普通の関数と同様に、goroutine関数が呼び出された後に、noGorutine関数が呼び出されていますね。
Goroutineスレッドの注意点
「go」キーワードは、呼び出した関数をスレッドにして実行します。
つまり、一旦スレッドを作成する必要があるということですね。
先ほどのプログラムは、100msごとにプログラムを停止していましたが、この処理を取り除くと意図しない動きを見せます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package main import ( "fmt" ) func noGoroutine() { for i := 0; i < 5; i++ { // time.Sleep(100 * time.Millisecond) fmt.Println("No goroutine") } } func goroutine() { for i := 0; i < 5; i++ { // time.Sleep(100 * time.Millisecond) fmt.Println("Yes goroutine") } } func main() { go goroutine() noGoroutine() } |
1 2 3 4 5 6 |
$ go run main.go No goroutine No goroutine No goroutine No goroutine No goroutine |
noGoroutine関数実行後に、プログラムが終了してしまいました。
これは、goroutineスレッドを形成している間に、プログラムが終了してしまう(noGoroutineのタスク終了)ためです。
Goroutineとsync.WaitGroup
スレッド実行前にプログラムが停止してしまう問題を避けるためにsync.WaitGroupを活用しましょう。
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 |
package main import ( "fmt" "sync" ) func noGoroutine() { for i := 0; i < 5; i++ { fmt.Println("No goroutine") } } // WaitGroupのアドレスを渡す func goroutine(wg *sync.WaitGroup) { // WaitGroupにタスク完了を通知 defer wg.Done() for i := 0; i < 5; i++ { fmt.Println("Yes goroutine") } } func main() { // WaitGroup作成 var wg sync.WaitGroup // スレッドが一つあることを伝える wg.Add(1) go goroutine(&wg) noGoroutine() // スレッド完了まで待機 wg.Wait() } |
少し難しいかもしれません。
まず、sync.WaitGroupで、WaitGroupを作成します。
その後、wg.Add(1)にて、スレッドを一つ作成することをWaitGroupに伝えます。
そして、並列化したい関数にWaitGroupのアドレスを渡してやり、呼び出し先で処理が完了したらwg.Doneで処理の完了を伝えます。
ちなみに「deferキーワード」をつけているので、関数終了時にwg.Doneが実行されます。
最後に、wg.Waitですが、wg.Addしたスレッドがwg.Doneで完了するまでプログラムの終了を待機させる働きをします。
プログラムを実行してみましょう。
1 2 3 4 5 6 7 8 9 10 11 |
$ go run main.go No goroutine No goroutine No goroutine No goroutine No goroutine Yes goroutine Yes goroutine Yes goroutine Yes goroutine Yes goroutine |
今度は、問題なく実行できましたね。
GoroutineとChannel
Goroutineで作成したスレッドでは、普通の方法ではデータのやり取りができません。理由は、並列処理で実行しているからです。
データをやり取りするためには、channelを用います。
簡単なサンプルを以下に示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package main import "fmt" func pro1(datas []int, c chan int) { sum := 0 for _, d := range datas { sum += d } c <- sum // channelに結果を送信 } func main() { datas := []int{1, 2, 3, 4, 5} // channelをmakeで作成 c := make(chan int) go pro1(datas, c) result := <-c // channelからデータを取り出す fmt.Println(result) } |
最初にchannelを作成する必要があります。
作成したチャネルをgoroutineで呼び出した関数の引数に渡し、呼び出し先で出力結果をchannelに追加します。その時に「channel<-」キーワードを使います。
そして、処理が完了後、「<-channel」にて処理結果を取り出します。
プログラムを実行してみましょう。
1 2 |
$ go run main.go 15 |
ちなみに、main関数はgoroutineスレッドです。Go言語ではmain関数がエントリーポイントになりますが、必ず最低一つはgoroutineスレッドが存在することになるわけですね。

ちなみに、「result := <-c 」にて、channelからデータが送信されてくるのを待ってるわけですが、channelからデータが送られてこないとプログラムがdeadlockを起こします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package main import "fmt" func pro1(datas []int, c chan int) { sum := 0 for _, d := range datas { sum += d } // c <- sum コメントアウト } func main() { datas := []int{1, 2, 3, 4, 5} // channelをmakeで作成 c := make(chan int) go pro1(datas, c) result := <-c // channelからデータを取り出す fmt.Println(result) } |
1 2 3 4 5 6 7 |
$ go run main.go fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive]: main.main() /Users/hoge/go/src/github.com/hoge/main.go:18 +0xdf exit status 2 |
また、以下のように複数のスレッドでchannelに値を送信できます。

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 |
package main import "fmt" func pro1(datas []int, c chan int) { sum := 0 for _, d := range datas { sum += d } c <- sum // channelに結果を送信 } func pro2(datas []int, c chan int) { sum := 0 for _, d := range datas { sum += d } c <- sum // channelに結果を送信 } func main() { datas := []int{1, 2, 3, 4, 5} // channelをmakeで作成 c := make(chan int) go pro1(datas, c) go pro2(datas, c) result1 := <-c // channelからデータを取り出す fmt.Println(result1) result2 := <-c // channelからデータを取り出す fmt.Println(result2) } |
1 2 3 |
$ go run main.go 15 15 |
make(chan int)には、[15, 15….]のようにデータがたまっていくのでしょうね。
次回
次回も引き続き、Goroutineを学びましょう!
最近のコメント