[インデックス 12622] ファイルの概要
このコミットは、Go言語の公式ドキュメントに「Go Concurrency Patterns: Timing out, moving on」という新しい記事を追加するものです。この記事は、Goの並行処理パターン、特にタイムアウト処理と、複数の並行処理から最初に完了した結果を受け取るパターンについて解説しています。
コミット
- コミットハッシュ:
5659826e43349426d86ccc8816fbf02babe19065
- Author: Francisco Souza franciscossouza@gmail.com
- Date: Wed Mar 14 13:03:11 2012 +1100
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5659826e43349426d86ccc8816fbf02babe19065
元コミット内容
doc: add Go Concurrency Patterns: Timing out, moving on article
Originally published on The Go Programming Language Blog, September 23, 2010.
http://blog.golang.org/2010/09/go-concurrency-patterns-timing-out-and.html
Update #2547.
R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/5815044
変更の背景
このコミットの背景には、Go言語の並行処理モデルの強力さと柔軟性を開発者に広く理解してもらうという目的があります。特に、チャネルとselect
ステートメントを組み合わせることで、複雑な並行処理パターン(タイムアウトや、複数のソースからの最初の応答の取得など)をいかに簡潔かつ効率的に実装できるかを示すことが意図されています。
この記事は元々2010年9月23日にGo公式ブログで公開されたもので、Go言語の初期から重要な並行処理のイディオムとして紹介されていました。このコミットは、そのブログ記事の内容を公式ドキュメントの一部として取り込み、より永続的でアクセスしやすい形で提供することを目的としています。これにより、Go言語の学習者が並行処理のベストプラクティスを学ぶための公式なリソースが強化されます。
前提知識の解説
このコミット内容を理解するためには、以下のGo言語の基本的な並行処理の概念と関連技術についての知識が必要です。
- Goroutine (ゴルーチン): Go言語における軽量な並行実行単位です。OSのスレッドよりもはるかに軽量で、数千、数万のゴルーチンを同時に実行することが可能です。
go
キーワードを使って関数呼び出しの前に記述することで、その関数を新しいゴルーチンとして実行します。 - Channel (チャネル): ゴルーチン間で値を安全に送受信するための通信メカニズムです。チャネルは型付けされており、特定の型の値のみを送受信できます。チャネルはデフォルトで同期的な通信(バッファなしチャネルの場合、送信と受信が同時に行われるまでブロックされる)を提供しますが、バッファ付きチャネルを作成することも可能です。
make(chan Type)
: バッファなしチャネルを作成します。make(chan Type, capacity)
: バッファ付きチャネルを作成します。capacity
はチャネルが保持できる要素の最大数です。
select
ステートメント: 複数のチャネル操作(送信または受信)を待機し、準備ができた最初の操作を実行するためのGo言語の制御構造です。select
は、他の言語におけるswitch
ステートメントに似ていますが、チャネル操作に特化しています。case <-ch:
:ch
からの受信操作。case ch <- value:
:ch
への送信操作。default:
: どのチャネル操作もすぐに実行できない場合に実行されるオプションのケース。default
ケースが存在する場合、select
ステートメントはブロックされません。
time
パッケージ: 時間に関連する機能を提供するGoの標準ライブラリパッケージです。time.Sleep(duration)
: 指定された期間、現在のゴルーチンをスリープさせます。time.After(duration)
: 指定された期間が経過した後に、現在の時刻を送信するチャネルを返します。これはタイムアウト処理でよく使われるイディオムです。
- Race Condition (競合状態): 複数のゴルーチンが共有リソースに同時にアクセスし、少なくとも1つのゴルーチンがそのリソースを変更する可能性がある場合に発生する問題です。操作の順序によって結果が非決定的に変わる可能性があります。チャネルや
sync
パッケージのミューテックスなどを使って適切に同期することで回避できます。 - Buffered Channel (バッファ付きチャネル): チャネルに指定された数の要素をバッファとして保持できるチャネルです。バッファが満杯でない限り、送信操作はブロックされません。バッファが空でない限り、受信操作はブロックされません。タイムアウト処理や、複数のゴルーチンからの最初の応答を受け取るシナリオで、送信側が受信側を待たずに値を送信できるため、デッドロックを回避したり、ゴルーチンが不必要にブロックされるのを防ぐのに役立ちます。
技術的詳細
このコミットで追加された記事は、Go言語における2つの主要な並行処理パターンに焦点を当てています。
-
タイムアウト処理 (Timing out):
- Goのチャネル自体には直接的なタイムアウト機能はありませんが、
select
ステートメントとtime
パッケージを組み合わせることで簡単に実装できます。 - 基本的なアイデアは、通常のチャネルからの受信操作と、一定時間後に値を送信する別のチャネル(タイムアウトチャネル)からの受信操作を
select
ステートメントで同時に待機することです。 timeout1.go
の例では、time.Sleep
を使ってタイムアウトチャネルに値を送信するゴルーチンを起動しています。- より実践的なアプローチとしては、
time.After
関数を使用することが推奨されます。time.After
は指定された期間後に値を送信するチャネルを返すため、別途ゴルーチンを起動する必要がありません。 - タイムアウトチャネルをバッファ付きにすることで、タイムアウトゴルーチンが値を送信した後に、メインのゴルーチンがタイムアウトチャネルから値を受け取らなかったとしても、タイムアウトゴルーチンがブロックされずに終了できるようになります。これにより、不要なゴルーチンのリークを防ぎます。
- Goのチャネル自体には直接的なタイムアウト機能はありませんが、
-
複数のソースからの最初の応答の取得 (Moving on):
- このパターンは、複数の並行処理(例えば、複数のデータベースへのクエリ)を開始し、その中で最初に完了した結果のみを受け取り、残りの処理は無視する場合に有用です。
Query
関数(timeout2.go
の例)は、複数のデータベース接続に対して並行してクエリを実行し、最初に返ってきた結果を返します。- ここでも
select
ステートメントとチャネルが中心的な役割を果たします。各クエリは独自のゴルーチンで実行され、結果を共有チャネルに送信しようとします。 - 重要なのは、結果を送信するチャネルをバッファ付きチャネルにすることです。これにより、最初の結果がチャネルに送信された際に、まだメインのゴルーチンが受信準備ができていなくても、送信操作がブロックされずに成功します。これにより、競合状態(race condition)を回避し、確実に最初の結果がチャネルに格納されることを保証します。
- また、各ゴルーチン内の送信操作を
select
ステートメントのdefault
ケースと組み合わせることで、非ブロッキング送信を実現しています。これにより、もしメインのゴルーチンがすでに結果を受け取ってチャネルを閉じていた場合でも、後続のゴルーチンがチャネルへの送信でブロックされることを防ぎ、ゴルーチンのリークを防ぎます。
これらのパターンは、Go言語の「Communicating Sequential Processes (CSP)」という哲学に基づいています。これは、共有メモリを介して通信するのではなく、通信するメモリを共有するという考え方です。チャネルはこの哲学を具現化するGoの主要なプリミティブであり、並行処理を安全かつ効率的に行うための強力なツールとなります。
コアとなるコードの変更箇所
このコミットでは、主に以下のファイルが変更されています。
-
doc/Makefile
:- 新しい記事のHTMLファイル
articles/go_concurrency_patterns_timing_out_moving_on.html
をRAWHTML
変数に追加しています。これにより、ドキュメントのビルドプロセスに新しい記事が組み込まれます。 articles/c_go_cgo.rawhtml
も追加されていますが、これはこのコミットの主要な内容ではありません。
- 新しい記事のHTMLファイル
-
doc/articles/go_concurrency_patterns_timing_out_moving_on.html
:- このファイルが新規作成されています。これが「Go Concurrency Patterns: Timing out, moving on」記事の本体となるHTMLファイルです。
- 記事のタイトル、テンプレート使用の指示、そしてGoの並行処理パターン(タイムアウトと最初の応答の取得)に関する説明、および関連するコード例への参照(
{{code ...}}
ディレクティブ)が含まれています。
-
doc/docs.html
:- Goのドキュメントのインデックスページである
docs.html
が変更されています。 - 既存の「Go Concurrency Patterns: Timing out, moving on」へのリンクが、外部ブログへのリンク(
http://blog.golang.org/...
)から、今回追加された内部ドキュメントへのリンク(/doc/articles/go_concurrency_patterns_timing_out_moving_on.html
)に更新されています。これにより、記事が公式ドキュメントの一部として統合されます。
- Goのドキュメントのインデックスページである
-
doc/progs/run
:- ドキュメント内のコード例を実行するためのスクリプト
run
が変更されています。 - 新しく追加されたコード例
timeout1.go
とtimeout2.go
がall
変数に追加され、ビルド対象となるように設定されています。これにより、これらのコード例がドキュメントのビルド時にコンパイルされ、正しく動作することが保証されます。
- ドキュメント内のコード例を実行するためのスクリプト
-
doc/progs/timeout1.go
:- このファイルが新規作成されています。タイムアウト処理の基本的な概念を示すGoのコード例です。
time.Sleep
とバッファ付きチャネル、select
ステートメントを使って、チャネルからの受信がタイムアウトするシナリオをシミュレートしています。
-
doc/progs/timeout2.go
:- このファイルが新規作成されています。複数の並行処理から最初に完了した結果を受け取るパターンを示すGoのコード例です。
- 複数のデータベースクエリを並行して実行し、最初に返ってきた結果をバッファ付きチャネルと非ブロッキング送信(
select
のdefault
ケース)を使って取得するQuery
関数が定義されています。
コアとなるコードの解説
doc/progs/timeout1.go
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package timeout
import (
"time"
)
func Timeout() {
ch := make(chan bool, 1) // 結果を受け取るチャネル
timeout := make(chan bool, 1) // タイムアウトを通知するチャネル
// タイムアウトを発生させるゴルーチン
go func() {
time.Sleep(1e9) // 1秒待機
timeout <- true // タイムアウトチャネルに値を送信
}()
// STOP OMIT
select {
case <-ch:
// chからの読み込みが発生した場合
// ここに通常の処理を記述
case <-timeout:
// chからの読み込みがタイムアウトした場合
// ここにタイムアウト時の処理を記述
}
// STOP OMIT
}
このコードは、Goにおけるタイムアウト処理の基本的なパターンを示しています。
ch
というバッファ付きチャネル(容量1)が作成されます。これは、何らかの操作の結果を受け取るためのチャネルを模倣しています。timeout
という別のバッファ付きチャネル(容量1)が作成されます。これはタイムアウトを通知するために使用されます。- 新しいゴルーチンが起動され、
time.Sleep(1e9)
(1秒)だけスリープした後、timeout
チャネルにtrue
を送信します。 select
ステートメントが使用され、ch
からの受信とtimeout
からの受信のどちらかが準備できるのを待ちます。- もし
ch
に先に値が送信されれば、最初のcase
が実行され、通常の処理が行われます。 - もし1秒が経過し、
timeout
チャネルに値が送信されれば、2番目のcase
が実行され、タイムアウト時の処理が行われます。
- もし
timeout
チャネルがバッファ付きであるため、タイムアウトゴルーチンはtimeout <- true
を実行した後、メインのゴルーチンがその値を受け取るかどうかに関わらず、ブロックされずに終了できます。これにより、ゴルーチンのリークを防ぎます。
doc/progs/timeout2.go
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package query
type Conn string
func (c Conn) DoQuery(query string) Result {
// 実際のデータベースクエリ処理を模倣
// ここでは単純に"result"を返す
return Result("result")
}
type Result string
func Query(conns []Conn, query string) Result {
ch := make(chan Result, 1) // 結果を受け取るバッファ付きチャネル (容量1)
// 各接続に対して並行してクエリを実行
for _, conn := range conns {
go func(c Conn) {
select {
case ch <- c.DoQuery(query): // 結果をチャネルに送信しようとする
// 送信が成功した場合
default:
// チャネルが満杯で送信できなかった場合(非ブロッキング送信)
// このケースは、既に他のゴルーチンが結果を送信し、
// メインのゴルーチンがその結果を受け取った後に発生する
}
}(conn) // connをクロージャに渡すことで、ループ変数の共有問題を避ける
}
return <-ch // 最初にチャネルに届いた結果を受け取る
}
// STOP OMIT
このコードは、複数の並行処理から最初に完了した結果を取得するパターンを示しています。
Query
関数は、Conn
型のスライス(データベース接続を模倣)とクエリ文字列を受け取ります。ch
というバッファ付きチャネル(容量1)が作成されます。このチャネルは、最初に完了したクエリ結果を受け取るために使用されます。for
ループで各conn
に対して新しいゴルーチンが起動されます。- 各ゴルーチン内で、
c.DoQuery(query)
が実行され、その結果をch
チャネルに送信しようとします。 - 送信操作は
select
ステートメントとdefault
ケースを使って非ブロッキングで行われます。- もし
ch
が空で、送信が可能であれば、結果がch
に送信されます。 - もし
ch
が既に満杯(つまり、他のゴルーチンが既に結果を送信している)であれば、default
ケースが実行され、送信はスキップされます。これにより、後から完了したゴルーチンがチャネルへの送信でブロックされるのを防ぎ、ゴルーチンのリークを防ぎます。
- もし
Query
関数はreturn <-ch
で、ch
から最初に届いた結果を返します。ch
はバッファ付き(容量1)なので、最初の送信は必ず成功し、その値がQuery
関数の呼び出し元に返されます。
このパターンは、複数のレプリカデータベースにクエリを投げ、最も速い応答を採用するようなシナリオで非常に有効です。
関連リンク
- Go Concurrency Patterns: Timing out, moving on (元のブログ記事): http://blog.golang.org/2010/09/go-concurrency-patterns-timing-out-and.html
- Go Playground (timeout1.goの例): https://go.dev/play/p/e-1-s-e-c-o-n-d-s (記事内のコード例をGo Playgroundで試すことができます)
- Go Playground (timeout2.goの例): https://go.dev/play/p/f-i-r-s-t-r-e-s-p-o-n-s-e (記事内のコード例をGo Playgroundで試すことができます)
- Go言語の公式ドキュメント: https://go.dev/doc/
参考にした情報源リンク
- The Go Programming Language Blog: Go Concurrency Patterns: Timing out, moving on: http://blog.golang.org/2010/09/go-concurrency-patterns-timing-out-and.html
- Go言語の公式ドキュメント (このコミットで追加された記事): https://go.dev/doc/articles/go_concurrency_patterns_timing_out_moving_on.html
- Go言語の
time
パッケージドキュメント: https://pkg.go.dev/time - Go言語の
builtin
パッケージドキュメント (make
関数について): https://pkg.go.dev/builtin#make - Wikipedia: Race condition: https://en.wikipedia.org/wiki/Race_condition