
Go並行処理のミニ書籍、Goroutineからコンテキストまで網羅
Anton Zhiyanovが公開したGo言語の並行処理をテーマとしたミニ書籍は、Goroutine、チャネル、select、パイプライン、時間管理、コンテキスト、データ競合、ミューテックスなど16の主要トピックをカバーし、各項目に対話的なコード例を含むPDF形式で提供されている。
Go言語の並行処理の基礎はgo キーワードで起動される「Goroutine」である。Goroutineはオペレーティングシステムのスレッドと比べて軽量であり、数百から数千のGoroutineを作成できる。Go実行時はGoroutineをOS スレッド間に分散させる。
基本的な並行処理パターン
チャネルはGoroutine間で値を渡すための機構である。「一つのGoroutineが何かを投げ込み、別のGoroutineが受け取るウィンドウのようなもの」として説明されている。チャネルへの値送信は同期的な操作で、送信側はレシーバーが値を受け取るまでブロックされる。close()でチャネルを閉じて全データ送信を通知できるが、「チャネルは1回だけ閉じられる」。閉じられたチャネルへの書き込みやnilチャネルの操作はパニックを引き起こす。
select文は「チャネル用に特別に設計されたswitch文のようなもの」で、複数のチャネル操作から利用可能なケースを選ぶ。複数のケースが準備完了している場合はランダムに1つが選ばれ、全ケースがブロックされており、かつデフォルトケースがない場合は待機する。
チャネルの詳細な動作
バッファ付きチャネルは「固定サイズバッファを備えたFIFOキューのように機能」し、バッファに空き容量があれば書き込みはブロックされない。バッファにデータがあれば読み込みもブロックされない。nilチャネルから読み書きすると「Goroutineが無期限にブロックされる」。
waitGroupはAdd(n)でカウンタをn増やし、Done()で1減らし、Wait()でカウンタがゼロになるまで待機する。sync.WaitGroupは管理下のGoroutineについて何も知らないため、呼び出し元が正しく追跡する必要がある。
タイミングと時間管理
time.After()はタイムアウト後に値を受け取るチャネルを返す。time.Timer のStop()はタイマーが未発火の場合は真を返す。「ループ内でタイマーを使う場合は、毎回新しいインスタンスを作成するより、1つのタイマーを作成してリセットする方が効率的」である。time.NewTicker()は指定間隔で発火するティッカーを作成し、Stop()で停止する必要がある。
コンテキストによるキャンセル管理
「コンテキストの主な目的は、手動またはタイムアウト・期限によって操作をキャンセルすること」である。context.WithCancel()は手動キャンセルを、context.WithTimeout()とcontext.WithDeadline()は時間ベースのキャンセルを作成する。「コンテキストはレイヤー状」であり、親と子のコンテキスト間で「より短いタイムアウトが常に優先される」。「子コンテキストは親のタイムアウトを短縮できるが、延長することはできない」。context.Done()チャネルはキャンセルのリッスンに用いられる。
データ競合と並行安全性
「データ競合は複数のGoroutineが共有データにアクセスし、少なくとも1つが修正する場合に発生」する。Goは-race フラグでアクセス可能なレース検出器を提供する。「チャネルは並行読み書きに対して安全であり、データ競合を引き起こさない」。レース状態は「複数のGoroutineからの予測不可能な操作順序が不正なシステム状態につながる場合に発生」し、Goのレース検出器はすべてを検出するわけではない。複合操作の保護にはミューテックスが有効である。
この記事は元記事の事実のみに基づいて自動生成されました。
筆者の見立て
- このミニ書籍は初心者向けガイドではなく、Go並行処理の簡潔な復習である、と論じている
- 実践的な演習を伴う体系的な学習を希望する場合は、著者の別の書籍を参照することを勧めている
出典
Anton Zhiyanov、「Go concurrency distilled」、https://antonz.org/go-concurrency-distilled/