はじめに
Go ならわかるシステムプログラミングを読みました。
- 作者:渋川 よしき
- 発売日: 2017/10/23
- メディア: 単行本(ソフトカバー)
僕は本で読みましたが、元々は Web で連載していたみたいで無料で読むことができます。(本の方がコンテンツは充実しています)
Go では、goroutine という並行処理機構があります。 goroutine での並行処理には、チャネルが用いられます。
チャネル( Channel )型は、チャネルオペレータの <- を用いて値の送受信ができる通り道です。 通常、片方が準備できるまで送受信はブロックされます。これにより、明確なロックや条件変数がなくても、goroutine の同期を可能にします。 https://go-tour-jp.appspot.com/concurrency/2
わかりにくい😔😔😔
チャネルは 3 つの性質があります。
- データを順序よく受け渡すためのデータ構造(キュー)
- goroutine 間で正しくデータを受け渡す同期機構
- 読み込み、書き込みの準備ができるまでブロックする機能
3 つめの、「読み込み、書き込みの準備ができるまでブロックする機能」が重要なのでまとめます。
読み込み、書き込みの準備ができるまでブロックする機能
チャネルによってブロックする例です。
goroutine 内でq <- struct{}{}
されるまで<-q
でブロックされます。
仮に<-q
がなかった場合、一瞬で Finish
が出力され main が終了します。
goroutine を制御するためにも、チャネルは必要です。
func main() { q := make(chan struct{}) go func() { for { fmt.Println("Waiting...") rand := rand.Float64() fmt.Println(rand) if rand < 0.5 { q <- struct{}{} // qに入れる } time.Sleep(1 * time.Second) } }() <-q // q に何か入るまで待つ fmt.Println("Finish") } // Output: // Waiting... // 0.6046602879796196 // Waiting... // 0.9405090880450124 // Waiting... // 0.6645600532184904 // Waiting... // 0.4377141871869802 // Finish // NOTE: Goのrandパッケージはシード値を設定しないと毎回同じ乱数が吐かれます
チャネルを使ってポーリングする
チャネルを使ってポーリングする例です。 goroutine を使うことで main で別のことをしながらポーリングすることができます。
func main() { q := make(chan struct{}, 2) // 1秒ごとにポーリングしている部分 go func() { for { fmt.Println("Waiting...") if requestApi() { q <- struct{}{} } time.Sleep(1 * time.Second) } }() // ポーリング中に別のことをする for { if len(q) > 0 { break } // q に溜まるまで他の事をしたい time.Sleep(time.Second) fmt.Println("Do something") } fmt.Println("Finish") } func requestApi() bool { time.Sleep(time.Second) if rand.Float64() < 0.2 { return true } return false }
コンテキスト
チャネルではなく、context パッケージを使うことでも非同期処理を制御することがきます。 キャンセルやタイムアウト処理を簡単に実装することができます。
func main() { // timeoutを3秒など短い時間にするとgoroutineが完了するのが間に合わずタイムアウトする ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) defer cancel() fmt.Println("goroutine start") go func() { for { if requestApi() { fmt.Println("goroutine done") cancel() } } }() <-ctx.Done() fmt.Println("main done") } func requestApi() bool { time.Sleep(time.Second) if rand.Float64() < 0.2 { return true } return false }
おわりに
Go を学び始めた時は、goroutine やチャネルはよくわからなかったですが、実際に自分でコードを書いているうちに理解が進みました。 そこまで並行処理を行う機会は多くないと感じていますが、よくあるケースとしてIO処理でのブロックがある時には使えると思います。 あとは、ライブラリの実装では使われていることが多いです。