お題
表題の通り。
goroutineを使った実装パターンを知りたければ下記を読むといい。
https://mattn.kaoriya.net/software/lang/go/20180531104907.htm
が、パターンはパターンとして、goroutine
をそもそも使わないケースから始めて、使うとどうなるか、うまく動かないケースも含めて試してみようと思う。
関連記事Index
開発環境
# OS
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="17.10 (Artful Aardvark)"
# 言語
$ go version
go version go1.10 linux/amd64
実践
$共通に使う関数$
100ミリ秒のスリープを入れつつ5回「ping」を出力するping()
関数と同様に「pong」を出力するpong()
関数を用意
func ping() {
for i := 0; i < 5; i++ {
fmt.Println("ping")
time.Sleep(100 * time.Millisecond)
}
}
func pong() {
for i := 0; i < 5; i++ {
fmt.Println(" pong")
time.Sleep(100 * time.Millisecond)
}
}
■関数の前に「go」と付けるだけでメイン関数と別の goroutine
が生成される
No.01: goroutine使わない版
func main() {
ping()
pong()
}
当然のように5回「ping」の後、5回「pong」を出力(何回やっても結果は同じ)
$ go run main.go
ping
ping
ping
ping
ping
pong
pong
pong
pong
pong
No.02: 先行して実行する関数をgoroutine化
func main() {
go ping()
pong()
}
ping()
関数はメイン処理をブロックせずに実行されるようになったため、即座にpong()
関数の実行も始まる。
<1回目>
$ go run main.go
pong
ping
pong
ping
pong
ping
pong
ping
pong
ping
<2回目>
$ go run main.go
pong
ping
ping
pong
ping
pong
ping
pong
ping
pong
<3回目>
$ go run main.go
pong
ping
ping
pong
pong
ping
ping
pong
ping
pong
No.03: 後攻の関数をgoroutine化
func main() {
ping()
go pong()
}
後攻の関数がメイン関数をブロックしなくなると、それ以降にメイン関数の処理がないため、メイン関数が終了する。
メイン関数が終了すると、goroutineも終了するため、5回の「ping」を出力したら(「pong」の出力は一切なしで)終了。
$ go run main.go
ping
ping
ping
ping
ping
No.04: 先行・後攻の関数をともにgoroutine化
func main() {
go ping()
go pong()
}
メイン関数をブロックする処理がなくなるのでping()
関数ないしpong()
関数が出力する前にメイン関数が終了する。
$ go run main.go
■生成した goroutine
の終了を待ってメイン関数を終了させる
No.05: 2つのgoroutineの終了をメイン関数で待機(Sleep編)
func main() {
go ping()
go pong()
time.Sleep(3 * time.Second)
}
goroutine
生成から3秒経過したら(goroutine
ごと)メイン関数が終了
ただし、このやり方は悪手。
$ go run main.go
ping
pong
ping
pong
ping
pong
pong
ping
pong
ping
No.06: 2つのgoroutineの終了をメイン関数で待機(WaitGroup編)
func main() {
wg := &sync.WaitGroup{}
wg.Add(1)
go ping(wg)
wg.Add(1)
go pong(wg)
wg.Wait()
}
func ping(wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 5; i++ {
fmt.Println("ping")
time.Sleep(100 * time.Millisecond)
}
}
func pong(wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 5; i++ {
fmt.Println(" pong")
time.Sleep(100 * time.Millisecond)
}
}
ちゃんとピンポン出る。
$ go run main.go
pong
ping
ping
pong
ping
pong
ping
pong
ping
pong
No.07: 2つのgoroutineの終了をメイン関数で待機(Channel編)
func main() {
ch := make(chan struct{}, 2)
go ping(ch)
go pong(ch)
<-ch
}
func ping(ch chan struct{}) {
for i := 0; i < 5; i++ {
fmt.Println("ping")
time.Sleep(100 * time.Millisecond)
}
ch <- struct{}{}
}
func pong(ch chan struct{}) {
for i := 0; i < 5; i++ {
fmt.Println(" pong")
time.Sleep(100 * time.Millisecond)
}
ch <- struct{}{}
}
$ go run main.go
pong
ping
ping
pong
pong
ping
pong
ping
pong
ping
まとめ
goroutineやChannelが真価を発揮する大分手前の時点だけど、今回はさわりだけ。