Goの並行パターン:タイムアウトと進行 #
Go Concurrency Patterns: Timing out, moving on by Andrew Gerrand
並行プログラミングにはイディオムがあります。良い例はタイムアウトです。
Goのチャンネルではタイムアウトを直接はサポートしていませんが、その実装は容易です。
たとえば、ch
チャンネルから値を受信したいけれど、1秒以上は待ちたくないという状況を考えてみましょう。
まずシグナル用のチャンネルを作り、そのチャンネルに送信する前に1秒待つゴルーチンを起動します。
timeout := make(chan bool, 1)
go func() {
time.Sleep(1 * time.Second)
timeout <- true
}()
その後、select
構文を使って ch
か timeout
を待つようにします。
もし1秒待っても ch
から何も来なければ、 timeout
のケースが選択され、ch
からの読み込みは破棄されます。
select {
case <-ch:
// chから読み込む
case <-timeout:
// chからの読み込みはタイムアウト
}
timeout
チャンネルは1つの値をバッファし、タイムアウトのゴルーチンがそのチャンネルに値を送り、終了できるようになっています。
このゴルーチンは、ch
から値が受け取られたかを知りません。(あるいは気にしていません)
つまりこのゴルーチンはch
からの読み込みがタイムアウトより前に起こったとしても、永遠には存在しえません。
timeout
チャンネルは最終的にガベージコレクタによって回収されます。
(この例ではゴルーチンとチャンネルの機構をデモするために time.Sleep
を使いました。
実際のプログラムでは time.After
という、チャンネルを返し、決まった時間のあとにそのチャンネルに値を送る関数を使うべきでしょう。)
このパターンの他の例を見てみましょう。この例では複数のレプリケーションされたデータベースから同時に読み込むプログラムを扱っています。 このプログラムでは、値は1つだけ必要で最初に来た値だけを取得すべきです。
Query
関数はデータベース接続のスライスと問い合わせの文字列を引数に取ります。
この関数は各データベースに並列に問い合わせ、最初に受信した結果を返します。
func Query(conns []Conn, query string) Result {
ch := make(chan Result, 1)
for _, conn := range conns {
go func(c Conn) {
select {
case ch <- c.DoQuery(query):
default:
}
}(conn)
}
return <-ch
}
この例では、クロージャーがノンブロッキングに送信します。これは、 default
ケース付きの select
構文内の送信操作を使うことで実現しています。
もし送信が出来なければ、直ちに default
ケースが選択されます。
送信をノンブロッキングにすることで、ループ内で立ち上げられたゴルーチンが1つも無駄に生存しないことが保証されます。
しかしながら、親の関数が値を受信する前に結果が来れば、チャンネルのバッファの準備ができていないため送信は失敗する可能性があります。
この問題は競合条件として知られるものの教科書的な例ですが、修正は些細なものです。
ch
チャンネルを(バッファの長さをmakeの第2引数に加えることで)バッファして、最初の送信処理が値を送れるように保証すれば良いだけです。
これによって送信処理は常に成功し、実行順に関係なく最初に到着した値が受信されるようになります。
この2つの例はGoがゴルーチン間の複雑なやりとりを表現する際の簡潔さを表しています。
By Andrew Gerrand
あわせて読みたい #
- Generating code
- Go Concurrency Patterns: Context
- Go Concurrency Patterns: Pipelines and cancellation
- Introducing the Go Race Detector
- Advanced Go Concurrency Patterns
- Go maps in action
- go fmt your code
- Concurrency is not parallelism
- Organizing Go code
- Go videos from Google I/O 2012
- Debugging Go programs with the GNU Debugger
- The Go image/draw package
- The Go image package
- The Laws of Reflection
- Error handling and Go
- “First Class Functions in Go”
- Profiling Go Programs
- A GIF decoder: an exercise in Go interfaces
- Introducing Gofix
- Godoc: documenting Go code
- Gobs of data
- C? Go? Cgo!
- JSON and Go
- Go Slices: usage and internals
- Defer, Panic, and Recover
- Share Memory By Communicating
- JSON-RPC: a tale of interfaces