Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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.OnceDo メソッド内で atomic.CompareAndSwapUint32 (CAS) が o.done フィールドの値を設定するために使用されていたことに対する疑問がありました。Once.Do の目的は、指定された関数 f を一度だけ実行することであり、o.done フィールドは関数が実行されたかどうかを示すフラグとして機能します。

従来のコードでは、o.done0 の場合に f() を実行し、その後 atomic.CompareAndSwapUint32(&o.done, 0, 1) を呼び出して o.done1 に設定していました。しかし、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.done1 に設定する段階では、既にミューテックスがロックされており、かつ o.done0 であることが保証されています(そうでなければ f() は実行されなかったはずです)。

したがって、この時点で o.done の値が 0 であることは確実であり、他のゴルーチンが同時に o.done を変更する可能性はありません。このような状況では、値を比較して交換するCAS操作は不要であり、単にアトミックに値を設定する atomic.StoreUint32 で十分です。

変更後のコードは以下のようになります。

atomic.StoreUint32(&o.done, 1)

この変更により、コードの意図がより明確になります。つまり、「o.done1 に設定する」という単純な操作であることが一目でわかります。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 が一度だけ実行されることを保証することです。

  1. o.m.Lock(): まず、Once オブジェクトに紐付けられたミューテックス m をロックします。これにより、複数のゴルーチンが同時に Do メソッドに入ろうとしても、一度に一つのゴルーチンだけがクリティカルセクション(if o.done == 0 ブロック内)に進むことができます。
  2. defer o.m.Unlock(): Do メソッドが終了する際にミューテックスをアンロックするように defer ステートメントで設定します。
  3. if o.done == 0: o.done フィールドが 0 であるか(つまり、まだ関数 f が実行されていないか)をチェックします。
  4. f(): もし o.done0 であれば、引数として渡された関数 f を実行します。この関数は、通常、初期化処理など、一度だけ実行されるべきロジックを含みます。
  5. atomic.StoreUint32(&o.done, 1): f() の実行後、o.done フィールドの値をアトミックに 1 に設定します。これにより、次回以降に Do メソッドが呼び出された際に o.done == 0 の条件が false となり、f() が再度実行されるのを防ぎます。

変更のポイントは、この o.done1 に設定する部分です。変更前は atomic.CompareAndSwapUint32(&o.done, 0, 1) を使用していましたが、ミューテックスによって保護されたこのコンテキストでは、o.done が確実に 0 であるため、比較を行う必要がなく、より単純な atomic.StoreUint32(&o.done, 1) で十分であると判断されました。これは、コードの意図をより明確にし、不必要な複雑さを排除するための改善です。

関連リンク

参考にした情報源リンク

  • 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.OnceDo メソッド内で atomic.CompareAndSwapUint32 (CAS) が o.done フィールドの値を設定するために使用されていたことに対する疑問がありました。Once.Do の目的は、指定された関数 f を一度だけ実行することであり、o.done フィールドは関数が実行されたかどうかを示すフラグとして機能します。

従来のコードでは、o.done0 の場合に f() を実行し、その後 atomic.CompareAndSwapUint32(&o.done, 0, 1) を呼び出して o.done1 に設定していました。しかし、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.done1 に設定する段階では、既にミューテックスがロックされており、かつ o.done0 であることが保証されています(そうでなければ f() は実行されなかったはずです)。

したがって、この時点で o.done の値が 0 であることは確実であり、他のゴルーチンが同時に o.done を変更する可能性はありません。このような状況では、値を比較して交換するCAS操作は不要であり、単にアトミックに値を設定する atomic.StoreUint32 で十分です。

変更後のコードは以下のようになります。

atomic.StoreUint32(&o.done, 1)

この変更により、コードの意図がより明確になります。つまり、「o.done1 に設定する」という単純な操作であることが一目でわかります。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 が一度だけ実行されることを保証することです。

  1. o.m.Lock(): まず、Once オブジェクトに紐付けられたミューテックス m をロックします。これにより、複数のゴルーチンが同時に Do メソッドに入ろうとしても、一度に一つのゴルーチンだけがクリティカルセクション(if o.done == 0 ブロック内)に進むことができます。
  2. defer o.m.Unlock(): Do メソッドが終了する際にミューテックスをアンロックするように defer ステートメントで設定します。
  3. if o.done == 0: o.done フィールドが 0 であるか(つまり、まだ関数 f が実行されていないか)をチェックします。
  4. f(): もし o.done0 であれば、引数として渡された関数 f を実行します。この関数は、通常、初期化処理など、一度だけ実行されるべきロジックを含みます。
  5. atomic.StoreUint32(&o.done, 1): f() の実行後、o.done フィールドの値をアトミックに 1 に設定します。これにより、次回以降に Do メソッドが呼び出された際に o.done == 0 の条件が false となり、f() が再度実行されるのを防ぎます。

変更のポイントは、この o.done1 に設定する部分です。変更前は atomic.CompareAndSwapUint32(&o.done, 0, 1) を使用していましたが、ミューテックスによって保護されたこのコンテキストでは、o.done が確実に 0 であるため、比較を行う必要がなく、より単純な atomic.StoreUint32(&o.done, 1) で十分であると判断されました。これは、コードの意図をより明確にし、不必要な複雑さを排除するための改善です。

関連リンク

参考にした情報源リンク

  • Go CL 6208057: sync: use atomic.Store in Once.Do: https://golang.org/cl/6208057
    • このコミットの元の変更リスト(Change List)であり、詳細な議論やレビューコメントが含まれている可能性があります。
  • Go言語の公式ドキュメント
  • Go言語のソースコード (src/pkg/sync/once.go)