誰もがシングルトンパターンをよく知っており、次のように定義されています。クラスは1つのオブジェクト(またはインスタンス)のみを作成でき、このクラスはシングルトンクラスです。このデザインパターンは、シングルトンデザインパターンまたはシングルトンと呼ばれます。略してパターン。
シングルトンモードは比較的簡単に理解できますが、実際に実装する際に考慮する必要のある詳細が多数あります。一般的な考慮事項は次のとおりです。
-
コンストラクターは、新規によるインスタンスの外部作成を回避するために、プライベートアクセス権を持っている必要があります
-
オブジェクトを作成するときは、スレッドセーフの問題を考慮してください
-
遅延読み込みをサポートするかどうかを検討する
-
getInstance()のパフォーマンスが高いかどうかを検討します(ロックするかどうか)
コード
文法が異なり、これらの点への注意度も異なります。Go言語の場合、sync.Once.Doを使用した記述方法は次のとおりです。この関数の目的は、1回だけ実行することです。
だから私たちは書くことができます:
type Single struct {
}
var (
once sync.Once
single *Single
)
func GetSingleInstance() *Single {
once.Do(func() {
single = &Single{
}
})
return single
}
リクエストの数に関係なく、Singleのインスタンスは1つだけです。
テスト
次に、シナリオについて考えてみましょう。100個のリクエストが突然GetSingleInstanceインターフェイスを同時にリクエストした場合、これらのリクエストはDoが実行されるのを待つのでしょうか、それともDoに関係なくシングルを返すだけでしょうか。
理論的には、Doの実行が完了するまで待つ必要があります。そうしないと、返されるシングルが空になり、重大なエラーが発生します。そう思いますが、テストしてみましょう。
package main
import (
"fmt"
"strconv"
"sync"
"time"
)
func main() {
var once sync.Once
onceBody := func() {
fmt.Println("Only once start")
time.Sleep(time.Second * 5)
fmt.Println("Only once end")
}
for i := 0; i < 5; i++ {
j := i
go func(int) {
fmt.Println(j)
once.Do(onceBody)
fmt.Println("lll" + strconv.Itoa(j))
}(j)
}
fmt.Println("finished")
time.Sleep(time.Second * 10)
}
テスト計画は非常に単純です。5つのゴルーチンを開始し、同時にDoを呼び出します。一度、Bodyが5秒間スリープを設定したら、出力を確認するだけで、ブロックされるかどうかを判断できます。
実行結果は以下のとおりです。
➜は、実行main.goが行くMYPROJECT
終了
0を
一度だけ起動する
2
4
3
1を
一度だけ、最後
lll2
lll4
lll0
lll1
lll3
Doが実行された後にのみすべてのゴルーチンが出力されることがわかります。これは、Doが呼び出されることを示していますが、実際に実行されるのは1つだけです。実際の実行が完了する前に、他のゴルーチンはブロックされます。
実際、隠れたリスクがあります。Doによって実行される関数に時間がかかると、多数のゴルーチンが蓄積されます。これは、プログラミング時に考慮する必要があります。
実装
Do機能はどのように実装されていますか?ソースコードを見てみましょう:
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 0 {
// Outlined slow-path to allow inlining of the fast-path.
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
複数のゴルーチンがdone値が0であることを確認する場合、doSlowと入力すると、1つのゴルーチンのみがロックを取得し、他のゴルーチンはブロックされません。
実用的なものは、より一般的な技術であり、主に相互排他ロック、セマフォ、および延期ですが、設計は依然として非常に巧妙です。これはGoの利点でもあります。競合を解決するためのロックの使用は高速で安全かつ便利ですが、パフォーマンスの問題を考慮する必要があります。
やっと
私の記事が気に入ったら、私の公式アカウント(プログラマーMala Tang)をフォローしてください。
私の個人的なブログは次のとおりです:https://shidawuhen.github.io/
以前の記事のレビュー:
技術
- Goデザインパターン(4)-コードの記述
- Goデザインパターン(3)-デザイン原則
- Goデザインパターン(2)-オブジェクト指向分析と設計
- 支払いアクセスの一般的な問題
- HTTP2.0基本チュートリアル
- Goデザインパターン(1)-文法
- MySQL開発仕様
- HTTPS構成の戦闘
- Goチャネル実装の原則
- Goタイマーの実装原理
- HTTPS接続プロセス
- 電流制限の実現2
- スパイクシステム
- 分散システムとコンセンサスプロトコル
- マイクロサービスのサービスフレームワークとレジストリ
- Beegoフレームワークの使用法
- マイクロサービスについて話す
- TCPパフォーマンスの最適化
- 電流制限の実現1
- Redisは分散ロックを実装しています
- Golangソースコードのバグ追跡
- トランザクションの原子性、一貫性、耐久性の実現原理
- 詳細なCDNリクエストプロセス
- 一般的なキャッシュ手法
- サードパーティの支払いに効率的に接続する方法
- ジンフレームワークの簡潔なバージョン
- InnoDBのロックとトランザクションの簡単な分析
- アルゴリズムの概要
研究ノート
考え