Goroutineとは
Goroutineとは、goのステートメントで、関数を指定することによって並行実行されるもの。
package main
import (
"fmt"
"time"
)
func main() {
go goroutine("Hello!")
hoge("World!")
}
func goroutine(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func hoge(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
> World!
> Hello!
> Hello!
> World!
> World!
> Hello!
> Hello!
> World!
> Hello!
> World!
→ 並行で実行したため、表示される順番がバラバラ
sync.WaitGroup
Goroutineに指定した関数は、メインスレッド(main関数)が終了してしまうと、実行されずに終わってしまう。
こうなると、並行で実行して欲しいのものができなくなってしまう。
上のコードだと、各関数のtime.Sleep()
をコメントアウトして実行すると、World
が5回表示されて終了する。
これを防ぐためにsync.WaitGroup
がある。
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(1)
go goroutine("Hello!", &wg)
hoge("World!")
wg.Wait()
}
func goroutine(s string, wg *sync.WaitGroup) {
for i := 0; i < 5; i++ {
fmt.Println(s)
}
wg.Done()
}
func hoge(s string) {
for i := 0; i < 5; i++ {
fmt.Println(s)
}
}
このように、wg
という変数を宣言して、wg.Add(1)
とすることで、1個の並行処理が実行されることを示す。また、wg
をgoroutineで指定する関数の引数に渡す。
wg.Wait()
を書くことでwg.Done()
が呼ばれるまで待ってくれる。この場合は、func goroutine()
にwg.Done()
を書いて、main関数にwg.Wait()
を書いたので、main関数はfunc goroutine()
が終わるまで待ってくれた。
wg.Add()
でwgをインクリメント、wg.Done()
でwgをデクリメントして、wg.Wait()
でwgのカウンタが0になるまで待つ、という感じ。なので、wg.Add()
が書かれていなかったりwg.Done()
が書かれていないと、エラーになる。
また、goroutine関数内でのwg.Done()
の呼び方として、
func goroutine(s string, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 5; i++ {
fmt.Println(s)
}
}
という呼び方もできる。こっちの方が、wg.Done()
を書き忘れなさそうな気がする。
チャネル
複数のgoroutine間でデータのやり取りを行うために、チャネルというものがある。
package main
import "fmt"
func sum(s []int, ch chan int) {
sum := 0
for _, v := range s {
sum += v
}
// チャネルにデータを送信
ch <- sum
}
func main() {
// チャネルの作成
ch := make(chan int)
s := []int{1, 1, 1}
go sum(s, ch)
// チャネルからデータを受信
sum := <-ch
fmt.Println(sum)
}
sum := <-ch
でブロッキングされるので、sync.WaitGroup()
で待機する必要はない。
上のコードだと、func sum()
のスレッドからmain関数のスレッドにsumの値が渡される