[インデックス 13059] ファイルの概要
このコミットは、Go言語の標準ライブラリ sync パッケージ内の Once.Do メソッドにおけるアトミック操作の変更に関するものです。具体的には、atomic.CompareAndSwapUint32 の代わりに atomic.StoreUint32 を使用するように修正されています。この変更は、パフォーマンスやセマンティクスに影響を与えるものではなく、コードの健全性を向上させることを目的としています。
コミット
commit 8c4c6c413facabf44b3ecd1fc44bd887fc710271
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Mon May 14 19:27:29 2012 +0400
sync: use atomic.Store in Once.Do
No perf/semantic changes, merely improves code health.
There were several questions as to why Once.Do uses
atomic.CompareAndSwap to do a store.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6208057
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8c4c6c413facabf44b3ecd1fc44bd887fc710271
元コミット内容
sync: use atomic.Store in Once.Do
No perf/semantic changes, merely improves code health.
There were several questions as to why Once.Do uses
atomic.CompareAndSwap to do a store.
変更の背景
この変更の背景には、sync.Once の Do メソッド内で atomic.CompareAndSwapUint32 (CAS) が o.done フィールドの値を設定するために使用されていたことに対する疑問がありました。Once.Do の目的は、指定された関数 f を一度だけ実行することであり、o.done フィールドは関数が実行されたかどうかを示すフラグとして機能します。
従来のコードでは、o.done が 0 の場合に f() を実行し、その後 atomic.CompareAndSwapUint32(&o.done, 0, 1) を呼び出して o.done を 1 に設定していました。しかし、f() が既に実行された後であれば、o.done の値は確実に 0 から 1 に変更されるべきであり、競合状態を考慮する必要がないため、CAS操作(比較と交換)は過剰な操作でした。
コミットメッセージにあるように、「なぜ Once.Do がストアのために atomic.CompareAndSwap を使うのか」という疑問が複数寄せられていました。この疑問は、コードの意図が不明瞭であること、またはより単純な操作で済むはずの箇所で複雑な操作が使われていることに対する懸念を示しています。
この変更は、パフォーマンスやセマンティクス(動作)に影響を与えるものではなく、単にコードの健全性(code health)を向上させることを目的としています。つまり、コードをより理解しやすく、意図を明確にし、不必要な複雑さを排除することが目的です。
前提知識の解説
Go言語の sync パッケージ
sync パッケージは、Go言語における基本的な同期プリミティブを提供します。これには、ミューテックス (sync.Mutex)、条件変数 (sync.Cond)、排他制御のための sync.WaitGroup などが含まれます。
sync.Once
sync.Once は、プログラムの実行中に特定の操作(通常は初期化処理)が一度だけ実行されることを保証するためのGo言語の同期プリミティブです。複数のゴルーチンが同時に Once.Do(f) を呼び出した場合でも、関数 f は一度だけ実行され、他のゴルーチンは f の完了を待ってから処理を続行します。これは、リソースの初期化やシングルトンパターンの実装などによく使用されます。
sync.Once の内部構造は通常、以下の要素を含みます。
m sync.Mutex: 複数のゴルーチンからのDoメソッドへの同時アクセスを保護するためのミューテックス。done uint32: 関数fが既に実行されたかどうかを示すフラグ。0は未実行、1は実行済みを示します。
sync/atomic パッケージ
sync/atomic パッケージは、低レベルのアトミック操作を提供します。アトミック操作とは、複数のCPUコアやゴルーチンから同時にアクセスされた場合でも、その操作全体が不可分(中断されない)であることを保証する操作です。これにより、ロックを使用せずに共有変数を安全に更新できます。
-
atomic.CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool): この関数は「比較と交換」(Compare-And-Swap, CAS)操作を実行します。addrが指すuint32の値がoldと等しい場合にのみ、その値をnewに更新します。更新が成功した場合はtrueを、失敗した場合はfalseを返します。CASは、ロックフリーなデータ構造やアルゴリズムを実装する際によく使用されます。 -
atomic.StoreUint32(addr *uint32, val uint32): この関数は、addrが指すuint32の値にvalをアトミックに書き込みます。これは単純なアトミックなストア操作であり、値の比較は行いません。
アトミック操作の重要性
並行プログラミングにおいて、複数のゴルーチンが同じメモリ領域にアクセスして読み書きを行う場合、競合状態(race condition)が発生する可能性があります。これにより、予期しない結果やデータ破損が生じることがあります。アトミック操作は、このような競合状態を防ぎ、共有データの整合性を保つための重要な手段です。
技術的詳細
このコミットの技術的な核心は、sync.Once.Do メソッドの内部で o.done フラグを 0 から 1 に設定する際のアトミック操作の選択です。
変更前のコードでは、以下の行がありました。
atomic.CompareAndSwapUint32(&o.done, 0, 1)
これは、o.done の現在値が 0 であることを確認し、もしそうであれば 1 に更新するという操作です。
しかし、Once.Do メソッドのロジックを考えると、このCAS操作は冗長でした。Once.Do の内部では、まず o.m.Lock() でミューテックスを取得し、o.done == 0 のチェックを行います。このチェックが true であれば、関数 f() が実行されます。f() の実行後、o.done を 1 に設定する段階では、既にミューテックスがロックされており、かつ o.done が 0 であることが保証されています(そうでなければ f() は実行されなかったはずです)。
したがって、この時点で o.done の値が 0 であることは確実であり、他のゴルーチンが同時に o.done を変更する可能性はありません。このような状況では、値を比較して交換するCAS操作は不要であり、単にアトミックに値を設定する atomic.StoreUint32 で十分です。
変更後のコードは以下のようになります。
atomic.StoreUint32(&o.done, 1)
この変更により、コードの意図がより明確になります。つまり、「o.done を 1 に設定する」という単純な操作であることが一目でわかります。CAS操作は、値が特定の期待値である場合にのみ更新したい、という複雑な条件がある場合に適していますが、このケースではそのような条件は存在しませんでした。
この修正は、パフォーマンス上の大きな改善をもたらすものではありませんが、コードの可読性と保守性を向上させます。不必要な複雑さを取り除くことで、将来のコードレビューやデバッグが容易になります。
コアとなるコードの変更箇所
変更は src/pkg/sync/once.go ファイルの Do メソッド内で行われました。
--- a/src/pkg/sync/once.go
+++ b/src/pkg/sync/once.go
@@ -38,6 +38,6 @@ func (o *Once) Do(f func()) {
defer o.m.Unlock()
if o.done == 0 {
f()
- atomic.CompareAndSwapUint32(&o.done, 0, 1)
+ atomic.StoreUint32(&o.done, 1)
}
}
コアとなるコードの解説
sync.Once 構造体の Do メソッドは、引数として f func() という関数を受け取ります。このメソッドの目的は、f が一度だけ実行されることを保証することです。
o.m.Lock(): まず、Onceオブジェクトに紐付けられたミューテックスmをロックします。これにより、複数のゴルーチンが同時にDoメソッドに入ろうとしても、一度に一つのゴルーチンだけがクリティカルセクション(if o.done == 0ブロック内)に進むことができます。defer o.m.Unlock():Doメソッドが終了する際にミューテックスをアンロックするようにdeferステートメントで設定します。if o.done == 0:o.doneフィールドが0であるか(つまり、まだ関数fが実行されていないか)をチェックします。f(): もしo.doneが0であれば、引数として渡された関数fを実行します。この関数は、通常、初期化処理など、一度だけ実行されるべきロジックを含みます。atomic.StoreUint32(&o.done, 1):f()の実行後、o.doneフィールドの値をアトミックに1に設定します。これにより、次回以降にDoメソッドが呼び出された際にo.done == 0の条件がfalseとなり、f()が再度実行されるのを防ぎます。
変更のポイントは、この o.done を 1 に設定する部分です。変更前は atomic.CompareAndSwapUint32(&o.done, 0, 1) を使用していましたが、ミューテックスによって保護されたこのコンテキストでは、o.done が確実に 0 であるため、比較を行う必要がなく、より単純な atomic.StoreUint32(&o.done, 1) で十分であると判断されました。これは、コードの意図をより明確にし、不必要な複雑さを排除するための改善です。
関連リンク
- Go言語
syncパッケージのドキュメント: https://pkg.go.dev/sync - Go言語
sync/atomicパッケージのドキュメント: https://pkg.go.dev/sync/atomic sync.Onceの詳細な解説 (Go by Example): https://gobyexample.com/once
参考にした情報源リンク
- Go CL 6208057:
sync: use atomic.Store in Once.Do: https://golang.org/cl/6208057- このコミットの元の変更リスト(Change List)であり、詳細な議論やレビューコメントが含まれている可能性があります。
- Go言語の公式ドキュメント
- Go言語のソースコード (
src/pkg/sync/once.go)# [インデックス 13059] ファイルの概要
このコミットは、Go言語の標準ライブラリ sync パッケージ内の Once.Do メソッドにおけるアトミック操作の変更に関するものです。具体的には、atomic.CompareAndSwapUint32 の代わりに atomic.StoreUint32 を使用するように修正されています。この変更は、パフォーマンスやセマンティクスに影響を与えるものではなく、コードの健全性を向上させることを目的としています。
コミット
commit 8c4c6c413facabf44b3ecd1fc44bd887fc710271
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Mon May 14 19:27:29 2012 +0400
sync: use atomic.Store in Once.Do
No perf/semantic changes, merely improves code health.
There were several questions as to why Once.Do uses
atomic.CompareAndSwap to do a store.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6208057
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8c4c6c413facabf44b3ecd1fc44bd887fc710271
元コミット内容
sync: use atomic.Store in Once.Do
No perf/semantic changes, merely improves code health.
There were several questions as to why Once.Do uses
atomic.CompareAndSwap to do a store.
変更の背景
この変更の背景には、sync.Once の Do メソッド内で atomic.CompareAndSwapUint32 (CAS) が o.done フィールドの値を設定するために使用されていたことに対する疑問がありました。Once.Do の目的は、指定された関数 f を一度だけ実行することであり、o.done フィールドは関数が実行されたかどうかを示すフラグとして機能します。
従来のコードでは、o.done が 0 の場合に f() を実行し、その後 atomic.CompareAndSwapUint32(&o.done, 0, 1) を呼び出して o.done を 1 に設定していました。しかし、f() が既に実行された後であれば、o.done の値は確実に 0 から 1 に変更されるべきであり、競合状態を考慮する必要がないため、CAS操作(比較と交換)は過剰な操作でした。
コミットメッセージにあるように、「なぜ Once.Do がストアのために atomic.CompareAndSwap を使うのか」という疑問が複数寄せられていました。この疑問は、コードの意図が不明瞭であること、またはより単純な操作で済むはずの箇所で複雑な操作が使われていることに対する懸念を示しています。
この変更は、パフォーマンスやセマンティクス(動作)に影響を与えるものではなく、単にコードの健全性(code health)を向上させることを目的としています。つまり、コードをより理解しやすく、意図を明確にし、不必要な複雑さを排除することが目的です。
前提知識の解説
Go言語の sync パッケージ
sync パッケージは、Go言語における基本的な同期プリミティブを提供します。これには、ミューテックス (sync.Mutex)、条件変数 (sync.Cond)、排他制御のための sync.WaitGroup などが含まれます。
sync.Once
sync.Once は、プログラムの実行中に特定の操作(通常は初期化処理)が一度だけ実行されることを保証するためのGo言語の同期プリミティブです。複数のゴルーチンが同時に Once.Do(f) を呼び出した場合でも、関数 f は一度だけ実行され、他のゴルーチンは f の完了を待ってから処理を続行します。これは、リソースの初期化やシングルトンパターンの実装などによく使用されます。
sync.Once の内部構造は通常、以下の要素を含みます。
m sync.Mutex: 複数のゴルーチンからのDoメソッドへの同時アクセスを保護するためのミューテックス。done uint32: 関数fが既に実行されたかどうかを示すフラグ。0は未実行、1は実行済みを示します。
sync/atomic パッケージ
sync/atomic パッケージは、低レベルのアトミック操作を提供します。アトミック操作とは、複数のCPUコアやゴルーチンから同時にアクセスされた場合でも、その操作全体が不可分(中断されない)であることを保証する操作です。これにより、ロックを使用せずに共有変数を安全に更新できます。
-
atomic.CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool): この関数は「比較と交換」(Compare-And-Swap, CAS)操作を実行します。addrが指すuint32の値がoldと等しい場合にのみ、その値をnewに更新します。更新が成功した場合はtrueを、失敗した場合はfalseを返します。CASは、ロックフリーなデータ構造やアルゴリズムを実装する際によく使用されます。 -
atomic.StoreUint32(addr *uint32, val uint32): この関数は、addrが指すuint32の値にvalをアトミックに書き込みます。これは単純なアトミックなストア操作であり、値の比較は行いません。
アトミック操作の重要性
並行プログラミングにおいて、複数のゴルーチンが同じメモリ領域にアクセスして読み書きを行う場合、競合状態(race condition)が発生する可能性があります。これにより、予期しない結果やデータ破損が生じることがあります。アトミック操作は、このような競合状態を防ぎ、共有データの整合性を保つための重要な手段です。
技術的詳細
このコミットの技術的な核心は、sync.Once.Do メソッドの内部で o.done フラグを 0 から 1 に設定する際のアトミック操作の選択です。
変更前のコードでは、以下の行がありました。
atomic.CompareAndSwapUint32(&o.done, 0, 1)
これは、o.done の現在値が 0 であることを確認し、もしそうであれば 1 に更新するという操作です。
しかし、Once.Do メソッドのロジックを考えると、このCAS操作は冗長でした。Once.Do の内部では、まず o.m.Lock() でミューテックスを取得し、o.done == 0 のチェックを行います。このチェックが true であれば、関数 f() が実行されます。f() の実行後、o.done を 1 に設定する段階では、既にミューテックスがロックされており、かつ o.done が 0 であることが保証されています(そうでなければ f() は実行されなかったはずです)。
したがって、この時点で o.done の値が 0 であることは確実であり、他のゴルーチンが同時に o.done を変更する可能性はありません。このような状況では、値を比較して交換するCAS操作は不要であり、単にアトミックに値を設定する atomic.StoreUint32 で十分です。
変更後のコードは以下のようになります。
atomic.StoreUint32(&o.done, 1)
この変更により、コードの意図がより明確になります。つまり、「o.done を 1 に設定する」という単純な操作であることが一目でわかります。CAS操作は、値が特定の期待値である場合にのみ更新したい、という複雑な条件がある場合に適していますが、このケースではそのような条件は存在しませんでした。
この修正は、パフォーマンス上の大きな改善をもたらすものではありませんが、コードの可読性と保守性を向上させます。不必要な複雑さを取り除くことで、将来のコードレビューやデバッグが容易になります。
コアとなるコードの変更箇所
変更は src/pkg/sync/once.go ファイルの Do メソッド内で行われました。
--- a/src/pkg/sync/once.go
+++ b/src/pkg/sync/once.go
@@ -38,6 +38,6 @@ func (o *Once) Do(f func()) {
defer o.m.Unlock()
if o.done == 0 {
f()
- atomic.CompareAndSwapUint32(&o.done, 0, 1)
+ atomic.StoreUint32(&o.done, 1)
}
}
コアとなるコードの解説
sync.Once 構造体の Do メソッドは、引数として f func() という関数を受け取ります。このメソッドの目的は、f が一度だけ実行されることを保証することです。
o.m.Lock(): まず、Onceオブジェクトに紐付けられたミューテックスmをロックします。これにより、複数のゴルーチンが同時にDoメソッドに入ろうとしても、一度に一つのゴルーチンだけがクリティカルセクション(if o.done == 0ブロック内)に進むことができます。defer o.m.Unlock():Doメソッドが終了する際にミューテックスをアンロックするようにdeferステートメントで設定します。if o.done == 0:o.doneフィールドが0であるか(つまり、まだ関数fが実行されていないか)をチェックします。f(): もしo.doneが0であれば、引数として渡された関数fを実行します。この関数は、通常、初期化処理など、一度だけ実行されるべきロジックを含みます。atomic.StoreUint32(&o.done, 1):f()の実行後、o.doneフィールドの値をアトミックに1に設定します。これにより、次回以降にDoメソッドが呼び出された際にo.done == 0の条件がfalseとなり、f()が再度実行されるのを防ぎます。
変更のポイントは、この o.done を 1 に設定する部分です。変更前は atomic.CompareAndSwapUint32(&o.done, 0, 1) を使用していましたが、ミューテックスによって保護されたこのコンテキストでは、o.done が確実に 0 であるため、比較を行う必要がなく、より単純な atomic.StoreUint32(&o.done, 1) で十分であると判断されました。これは、コードの意図をより明確にし、不必要な複雑さを排除するための改善です。
関連リンク
- Go言語
syncパッケージのドキュメント: https://pkg.go.dev/sync - Go言語
sync/atomicパッケージのドキュメント: https://pkg.go.dev/sync/atomic sync.Onceの詳細な解説 (Go by Example): https://gobyexample.com/once
参考にした情報源リンク
- Go CL 6208057:
sync: use atomic.Store in Once.Do: https://golang.org/cl/6208057- このコミットの元の変更リスト(Change List)であり、詳細な議論やレビューコメントが含まれている可能性があります。
- Go言語の公式ドキュメント
- Go言語のソースコード (
src/pkg/sync/once.go)