[インデックス 12311] ファイルの概要
このコミットは、Go言語の標準ライブラリ sync
パッケージに sync.Once
型の使用例を追加するものです。sync.Once
は、並行処理環境において、ある処理が一度だけ実行されることを保証するためのプリミティブであり、このコミットではその典型的な利用方法を示す ExampleOnce
関数が src/pkg/sync/example_test.go
に追加されています。
コミット
commit 2295554db6503cc47eb0cfb69c59cc5b740f5f0e
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Thu Mar 1 22:16:20 2012 +0400
sync: add Once example
R=golang-dev, rsc, bradfitz
CC=golang-dev
https://golang.org/cl/5715046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2295554db6503cc47eb0cfb69c59cc5b740f5f0e
元コミット内容
sync: add Once example
このコミットは、Go言語の sync
パッケージに Once
型の利用例を追加します。
変更の背景
Go言語は並行処理を強力にサポートしており、goroutine
と channel
を用いた並行プログラミングが容易です。しかし、並行処理においては、リソースの初期化や設定など、特定の処理がアプリケーションのライフサイクル中に一度だけ実行されることを保証する必要がある場面が頻繁に発生します。例えば、データベース接続の確立、設定ファイルの読み込み、シングルトンインスタンスの生成などが挙げられます。
このような「一度だけ実行」という要件を満たすために、手動でミューテックスやアトミック操作を組み合わせて実装することも可能ですが、それは複雑でバグを招きやすいものです。sync.Once
は、この一般的なパターンを安全かつ効率的に実現するための標準的なプリミティブとして提供されています。
このコミットが作成された背景には、sync.Once
の重要性にもかかわらず、その具体的な使用方法を示す公式のコード例が不足していたという点があります。_test.go
ファイル内の Example
関数は、Goのドキュメンテーションツール go doc
や godoc
によって自動的に抽出され、パッケージのドキュメントに組み込まれるため、ユーザーが sync.Once
の使い方を理解する上で非常に役立ちます。この例を追加することで、開発者は sync.Once
の正しい利用パターンを容易に学習できるようになります。
前提知識の解説
Go言語の並行処理
Go言語は、軽量なスレッドである「goroutine(ゴルーチン)」と、goroutine間の安全な通信を可能にする「channel(チャネル)」を言語レベルでサポートしています。
- Goroutine:
go
キーワードを使って関数呼び出しの前に記述することで、その関数を新しいgoroutineとして実行します。これにより、複数の処理を並行して実行できます。 - Channel: goroutine間で値を送受信するための通信メカニズムです。チャネルは、データの同期と通信を同時に行い、共有メモリによる競合状態(race condition)を避けるための推奨される方法です。
sync
パッケージ
sync
パッケージは、Go言語における基本的な同期プリミティブを提供します。これには、ミューテックス (sync.Mutex
)、条件変数 (sync.Cond
)、排他制御のための sync.WaitGroup
などが含まれます。そして、このコミットの主題である sync.Once
もその一つです。
sync.Once
sync.Once
は、プログラムの実行中に特定の関数が一度だけ実行されることを保証するための構造体です。複数のgoroutineから同時に Do
メソッドが呼び出されたとしても、引数として渡された関数 f
は一度だけ実行され、その後の呼び出しでは f
は実行されません。これは、初期化処理やシングルトンパターンの実装に非常に有用です。
sync.Once
の内部では、アトミック操作とミューテックスを組み合わせて、この「一度だけ実行」という保証を実現しています。
Example
関数
Go言語のテストファイル(_test.go
で終わるファイル)には、Example
というプレフィックスを持つ関数を記述できます。これらの関数は、パッケージのドキュメントにコード例として表示され、go test
コマンドで実行される際に、コメントとして記述された Output:
と実際の標準出力が一致するかどうかが検証されます。これにより、ドキュメントのコード例が常に正しく動作することが保証されます。
技術的詳細
このコミットで追加された ExampleOnce
関数は、sync.Once
の典型的な使用パターンを明確に示しています。
-
sync.Once
インスタンスの宣言:var once sync.Once
sync.Once
型の変数を宣言します。Goでは、構造体のゼロ値が有効な初期値となるため、明示的な初期化は不要です。 -
一度だけ実行したい処理の定義:
onceBody := func() { fmt.Printf("Only once\\n") }
once.Do
メソッドに渡す関数リテラルonceBody
を定義しています。この関数は、sync.Once
によって一度だけ実行されることが保証される処理を含みます。ここでは、単純に "Only once" という文字列を標準出力に出力するだけです。 -
複数のgoroutineからの
Do
メソッド呼び出し:done := make(chan bool) for i := 0; i < 10; i++ { go func() { once.Do(onceBody) done <- true }() }
この部分が
sync.Once
の動作をデモンストレーションする核心です。done := make(chan bool)
: 10個のgoroutineがすべて完了したことを待つためのチャネルを作成します。for i := 0; i < 10; i++
: 10個のgoroutineを起動します。go func() { ... }()
: 各goroutine内で無名関数を実行します。once.Do(onceBody)
: 各goroutineはonce.Do
メソッドを呼び出し、onceBody
関数を引数として渡します。sync.Once
の保証により、たとえ10個のgoroutineが同時にこの行に到達したとしても、onceBody
は一度だけ実行されます。done <- true
:once.Do
の呼び出しが完了した後、各goroutineはdone
チャネルに値を送信し、自身の完了を通知します。
-
すべてのgoroutineの完了を待機:
for i := 0; i < 10; i++ { <-done }
メインgoroutineは、
done
チャネルから10回値を受信することで、起動したすべてのgoroutineがonce.Do
の呼び出しを終えたことを確認し、プログラムが終了する前にすべての並行処理が完了するのを待ちます。 -
期待される出力の指定:
// Output: // Only once
これは
Example
関数の特別なコメントで、この関数を実行した際の標準出力が "Only once" であることを期待していることを示します。これにより、sync.Once
が実際に一度だけ処理を実行したことが検証されます。
この例は、sync.Once
が複数の並行実行パスから呼び出されても、指定された初期化関数が一度だけ実行されるという、その主要な特性を簡潔かつ効果的に示しています。
コアとなるコードの変更箇所
src/pkg/sync/example_test.go
ファイルに以下の変更が加えられました。
--- a/src/pkg/sync/example_test.go
+++ b/src/pkg/sync/example_test.go
@@ -5,6 +5,7 @@
package sync_test
import (
+ "fmt"
"net/http"
"sync"
)
@@ -32,3 +33,22 @@ func ExampleWaitGroup() {
// Wait for all HTTP fetches to complete.
wg.Wait()
}\n+\n+func ExampleOnce() {\n+\tvar once sync.Once\n+\tonceBody := func() {\n+\t\tfmt.Printf("Only once\\n")\n+\t}\n+\tdone := make(chan bool)\n+\tfor i := 0; i < 10; i++ {\n+\t\tgo func() {\n+\t\t\tonce.Do(onceBody)\n+\t\t\tdone <- true\n+\t\t}()\n+\t}\n+\tfor i := 0; i < 10; i++ {\n+\t\t<-done\n+\t}\n+\t// Output:\n+\t// Only once\n+}\n```
## コアとなるコードの解説
変更の中心は、`ExampleOnce` 関数の追加です。
1. **`import ("fmt")` の追加**: `fmt.Printf` を使用するために `fmt` パッケージがインポートされています。
2. **`func ExampleOnce()` の定義**:
* `var once sync.Once`: `sync.Once` 型の変数 `once` を宣言します。これは、一度だけ実行される処理を管理するためのオブジェクトです。
* `onceBody := func() { fmt.Printf("Only once\\n") }`: `once.Do` メソッドに渡される関数リテラル `onceBody` を定義します。この関数は、"Only once" という文字列を標準出力に出力する処理を含みます。
* `done := make(chan bool)`: 複数のgoroutineの完了を待つためのチャネル `done` を作成します。
* `for i := 0; i < 10; i++ { ... }`: 10個のgoroutineを起動するループです。
* `go func() { ... }()`: 各イテレーションで新しいgoroutineを起動します。
* `once.Do(onceBody)`: これが `sync.Once` の核心です。10個のgoroutineが同時にこの行を実行しようとしますが、`onceBody` 関数は `once` オブジェクトによって一度だけ実行されることが保証されます。
* `done <- true`: `once.Do` の呼び出しが完了した後、goroutineは `done` チャネルにシグナルを送信し、自身の完了を通知します。
* `for i := 0; i < 10; i++ { <-done }`: メインgoroutineは、`done` チャネルから10回値を受信することで、すべてのgoroutineが `once.Do` の呼び出しを終えるのを待ちます。これにより、プログラムが終了する前にすべての並行処理が完了することが保証されます。
* `// Output:\n// Only once`: これは `go test` が `ExampleOnce` 関数を実行した際に期待される標準出力です。このコメントがあることで、`go test` は実際の出力とこのコメントの内容を比較し、テストが成功したかどうかを判断します。これにより、`sync.Once` が正しく機能し、`onceBody` が一度だけ実行されたことが検証されます。
この追加により、Go言語の公式ドキュメントに `sync.Once` の明確で実行可能な例が提供され、開発者がこの重要な並行処理プリミティブをより簡単に理解し、利用できるようになりました。
## 関連リンク
* Go言語 `sync` パッケージのドキュメント: [https://pkg.go.dev/sync](https://pkg.go.dev/sync)
* `sync.Once` のドキュメント: [https://pkg.go.dev/sync#Once](https://pkg.go.dev/sync#Once)
* Go言語の `Example` 関数に関するドキュメント: [https://go.dev/blog/examples](https://go.dev/blog/examples)
## 参考にした情報源リンク
* Go言語公式ドキュメント
* Go言語のソースコード (特に `src/pkg/sync/once.go` および `src/pkg/sync/example_test.go`)
* コミット情報から得られた Go CL (Change List) へのリンク: [https://golang.org/cl/5715046](https://golang.org/cl/5715046)