- 概要
- 投げっぱなし(完了検知なし)
- doneチャネルを使って完了検知
- sync.WaitGroup を使って完了検知
- context.Context を用いて完了検知(context.Context.WithCancel)
- context.Context を用いてタイムアウト付きで完了検知(context.Context.WithTimeout)
- おすすめ書籍
概要
以下、個人的なメモです。
ゴルーチンはGCの対象とならないので、きっちり停止させることがとても大事。
unbufferedなチャネルを使っていて、他方が受信してくれていないので、ずっと送信できずにスタックしてるとかが要注意。
で、同じくらい大事なのが、完了したことを検知すること。(エラー発生時の伝播も大事だけどこれは別の機会にメモ)
いろいろやり方があるけど、とりあえず自分用にメモ。
投げっぱなし(完了検知なし)
あまりしないけど、起動するゴルーチンの生存期間がアプリケーションの生存期間と同じ場合とかだと投げっぱなしにするときもある。
package goroutines import "github.com/devlights/gomy/output" // NonStop -- ゴルーチンを待ち合わせ無しで走らせるサンプルです. // // 投げっぱなしのゴルーチンを作る場合に使います。 // 通常待ち合わせ無しの非同期処理は行うべきではありません。 func NonStop() error { go func() { output.Stdoutl("[goroutine] ", "This line may not be printed") }() // 上記のゴルーチンは待ち合わせをしていないので出力されない可能性がある。 // (出力する前にメインゴルーチンが終わる可能性がある) return nil }
実行すると以下のようになります。
$ make run go run github.com/devlights/try-golang/cmd/trygolang -onetime -example "" ENTER EXAMPLE NAME: goroutines_nonstop [Name] "goroutines_nonstop" [Elapsed] 29.906µs
doneチャネルを使って完了検知
doneチャネル、つまり、chan struct{}
を使って、完了検知。チャネルをクローズすることで完了をブロードキャストする。
package goroutines import "github.com/devlights/gomy/output" // WithDoneChannel -- doneチャネルを用いて待ち合わせを行うサンプルです. func WithDoneChannel() error { done := func() <-chan struct{} { done := make(chan struct{}) go func() { defer close(done) output.Stdoutl("[goroutine]", "This line is printed") }() return done }() <-done return nil }
実行すると以下のようになります。
$ make run go run github.com/devlights/try-golang/cmd/trygolang -onetime -example "" ENTER EXAMPLE NAME: goroutines_with_done_channel [Name] "goroutines_with_done_channel" [goroutine] This line is printed [Elapsed] 50.388µs
sync.WaitGroup を使って完了検知
手軽に使える方法。起動するゴルーチンの数が不定の場合とか、とりあえず全部完了するまで待ちたいときとか便利。
package goroutines import ( "sync" "github.com/devlights/gomy/output" ) // WithWaitGroup -- sync.WaitGroupを用いて待ち合わせを行うパターンです. func WithWaitGroup() error { var ( wg sync.WaitGroup ) wg.Add(1) go func() { defer wg.Done() output.Stdoutl("[goroutine]", "This line is printed") }() wg.Wait() return nil }
実行すると以下のようになります。
$ make run go run github.com/devlights/try-golang/cmd/trygolang -onetime -example "" ENTER EXAMPLE NAME: goroutines_with_waitgroup [Name] "goroutines_with_waitgroup" [goroutine] This line is printed [Elapsed] 75.565µs
context.Context を用いて完了検知(context.Context.WithCancel)
最近のGoではデフォルトで利用されることが多い context.Context 。context.Done() が返すのは done チャネルになっている。
package goroutines import ( "context" "github.com/devlights/gomy/output" ) // WithContextCancel -- context.Contextを用いて待ち合わせを行うサンプルです. func WithContextCancel() error { var ( rootCtx = context.Background() mainCtx, mainCancel = context.WithCancel(rootCtx) ) defer mainCancel() ctx := func(pCtx context.Context) context.Context { ctx, cancel := context.WithCancel(pCtx) go func() { defer cancel() output.Stdoutl("[goroutine]", "This line is printed") }() return ctx }(mainCtx) <-ctx.Done() return nil }
実行すると以下のようになります。
$ make run go run github.com/devlights/try-golang/cmd/trygolang -onetime -example "" ENTER EXAMPLE NAME: goroutines_with_context_cancel [Name] "goroutines_with_context_cancel" [goroutine] This line is printed [Elapsed] 44.575µs
context.Context を用いてタイムアウト付きで完了検知(context.Context.WithTimeout)
実務では、完了するまでずっと動いていて良い処理というのはあまり無い。実際には何らかの生存期間を設けて、その中で動作させることが多い。
doneチャネルだけで処理しようとすると、別途タイムアウト用のチャネルなどを設けないといけないのでちょっと面倒。
その点、context.Contextだとサクッと出来るので楽。
package goroutines import ( "context" "time" "github.com/devlights/gomy/output" ) // WithContextTimeout -- context.Contextを用いてタイムアウト付きで待ち合わせを行うサンプルです func WithContextTimeout() error { // 処理内で利用する共通関数 var ( iter = func(n int) []struct{} { return make([]struct{}, n) } now = func() int64 { return time.Now().UTC().Unix() } ) // コンテキスト定義 var ( rootCtx = context.Background() mainCtx, mainCancel = context.WithTimeout(rootCtx, 2*time.Second) procCtx, procCancel = context.WithTimeout(mainCtx, 1*time.Second) ) defer mainCancel() defer procCancel() // --------------------------------------------------- // 以下の仕様とする // - アプリケーション全体の生存期間は2秒間 // - 非同期起動するゴルーチン全体の生存期間は1秒間 // --------------------------------------------------- ctx := func(pCtx context.Context) context.Context { ctx, cancel := context.WithCancel(pCtx) go func() { defer cancel() for i := range iter(10) { select { case <-ctx.Done(): output.Stdoutl("[ctx inside]", "done", now()) return default: } output.Stdoutl("[goroutine]", i) time.Sleep(200 * time.Millisecond) } }() return ctx }(procCtx) // 待ち合わせしながら経過出力 var ( doneCtx, doneProc, doneMain = ctx.Done(), procCtx.Done(), mainCtx.Done() ) LOOP: for { select { case <-doneCtx: output.Stdoutl("[ctx]", "done", now()) doneCtx = nil case <-doneProc: output.Stdoutl("[proc]", "done", now()) doneProc = nil case <-doneMain: output.Stdoutl("[main]", "done", now()) break LOOP } } return nil }
実行すると以下のようになります。
$ make run go run github.com/devlights/try-golang/cmd/trygolang -onetime -example "" ENTER EXAMPLE NAME: goroutines_with_context_timeout [Name] "goroutines_with_context_timeout" [goroutine] 0 [goroutine] 1 [goroutine] 2 [goroutine] 3 [goroutine] 4 [proc] done 1605492499 [ctx] done 1605492499 [ctx inside] done 1605492499 [main] done 1605492500 [Elapsed] 2.000258303s
おすすめ書籍
自分が読んだGo関連の本で、いい本って感じたものです。

- 作者:Katherine Cox-Buday
- 発売日: 2018/10/26
- メディア: 単行本(ソフトカバー)

- 作者:松尾 愛賀
- 発売日: 2016/04/15
- メディア: 単行本(ソフトカバー)

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
- 作者:Alan A.A. Donovan,Brian W. Kernighan
- 発売日: 2016/06/20
- メディア: 単行本(ソフトカバー)
過去の記事については、以下のページからご参照下さい。
- いろいろ備忘録日記まとめ
サンプルコードは、以下の場所で公開しています。
- いろいろ備忘録日記サンプルソース置き場