AtomicStoreUint32 と Sync.Once での割り当て
Go の sync.Once 型のソース コードを調べているときに、その使用方法に関する疑問が生じます。 atomic.StoreUint32 と、done フラグを設定するための通常の割り当ての比較。
不正な実装:
元のソース コードには不正な実装が含まれていました:
<code class="go">func (o *Once) Do(f func()) { if atomic.CompareAndSwapUint32(&o.done, 0, 1) { f() } }</code>
この実装では、関数が返されたときに f が完了していることを保証できません。同時呼び出しにより、最初の呼び出しが完了したと仮定して、勝者が f を実行し、2 番目の呼び出し元がすぐに戻る可能性がありますが、そうでない場合もあります。
正しい実装:
この問題を修正するために、現在の実装では atomic.StoreUint32 をミューテックスと組み合わせて使用しています:
<code class="go">func (o *Once) Do(f func()) { if atomic.LoadUint32(&o.done) == 0 { o.doSlow(f) } }</code>
<code class="go">func (o *Once) doSlow(f func()) { o.m.Lock() defer o.m.Unlock() if o.done == 0 { defer atomic.StoreUint32(&o.done, 1) f() } }</code>
なぜ AtomicStoreUint32 なのか?
アトミックの使用.StoreUint32 は、f の完了後に他のゴルーチンが o.done への変更を監視できるようにするために必要です。特定のアーキテクチャではプリミティブ割り当てがアトミックである可能性がありますが、Go のメモリ モデルでは、サポートされているすべてのアーキテクチャにわたってアトミック操作を保証するためにアトミック パッケージを使用する必要があります。
完了フラグへのアクセス:
目標は、ミューテックス外での完了フラグへのアクセスが安全であることを保証することです。したがって、ミューテックスによるロックの代わりにアトミック操作が利用されます。この最適化により高速パスの効率が向上し、高トラフィックのシナリオで sync.Once を展開できるようになります。
doSlow のミューテックス:
doSlow 内のミューテックスにより、 o.done が設定される前に f を実行する呼び出し元は 1 人だけです。 atomic.StoreUint32 は、ミューテックスのクリティカル セクション外で atomic.LoadUint32 と同時に発生する可能性があるため、フラグの書き込みに使用されます。
同時書き込みと読み取り:
直接読み取りdoSlow の o.done は、ミューテックス保護により安全です。さらに、o.done を atomic.LoadUint32 と同時に読み取ることは、両方の操作に読み取りのみが含まれるため、安全です。
以上がなぜ `sync.Once` は `done` フラグの通常の代入ではなく `atomic.StoreUint32` を使用するのでしょうか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。