この記事では、主に C# シングルトン モードの実装とパフォーマンスの比較に関する関連情報を紹介します。6 つの実装方法を詳しく紹介します。必要な方は参考にしてください。
単一のケースは、次のことができるクラスのみを指します。 1 つのインスタンスを持つ (C# では、より正確には、各 AppDomain に 1 つのインスタンスしか持てないクラス) は、ソフトウェア エンジニアリングで最も一般的に使用されるパターンの 1 つであり、最初のユーザーはこのクラスのインスタンスを作成した後、このクラスを使用する場合は、以前に作成されたインスタンスのみを使用でき、新しいインスタンスを作成することはできません。通常、C# でのシングルトン実装メソッドがいくつか導入されており、それらの間のスレッドの安全性とパフォーマンスの違いが異なります。シングルトンを実装するにはさまざまな方法がありますが、最も単純な実装 (非遅延ロード、非スレッド セーフ、低効率) から、遅延ロード、スレッド セーフ、効率的な実装まで、さまざまな方法があります。これらにはすべて、いくつかの基本的な共通点があります:
シングルトン クラスはすべて、引数のないプライベート コンストラクターを 1 つだけ持ちます複数の実装
1 つの非スレッド セーフティ
//Bad code! Do not use! public sealed class Singleton { private static Singleton instance = null; private Singleton() { } public static Singleton instance { get { if (instance == null) { instance = new Singleton(); } return instance; } } }
2 番目の単純なスレッドセーフ実装
public sealed class Singleton { private static Singleton instance = null; private static readonly object padlock = new object(); Singleton() { } public static Singleton Instance { get { lock (padlock) { if (instance == null) { instance = new Singleton(); } return instance; } } } }
ここでは新しいプライベート オブジェクトを使用していることに注意してください。シングルトンを直接ロックするのではなく、インスタンスの南京錠を使用してロック操作を実装します。これにより、潜在的なリスクが生じる可能性があります。この型はパブリックであるため、理論的には、これを直接ロックするとパフォーマンス上の問題が発生し、さらにはデッドロックが発生する可能性があります。 : C# では、同じスレッドでオブジェクトをロックすることができますが、異なるスレッドが同時にロックされると、スレッド待ちが発生したり、深刻なデッドロック状態が発生したりする可能性があります。そのため、ロックを使用する場合は、ロックを使用するようにしてください。ロックするクラス内のプライベート変数を選択すると、上記の状況が発生するのを回避できます
3重検証スレッドセーフティ実装
public sealed calss Singleton { private static Singleton instance = null; private static readonly object padlock = new object(); Singleton() { } public static Singleton Instance { get { if (instance == null) { lock (padlock) { if (instance == null) { instance = new Singleton(); } } } return instance; } } }
1 は Java では機能しません。 (具体的な理由については原文をご覧ください。ここではあまり理解できません)
2 プログラマーは自分で実装するときに間違いを犯しがちです。このモードでコードを独自に変更する場合は、ダブル チェックのロジックが比較的複雑で、考えが不十分なために間違いを犯しやすいため、十分に注意してください。
ロックを使用しない 4 つのスレッドセーフな実装
public sealed class Singleton { //在Singleton第一次被调用时会执行instance的初始化 private static readonly Singleton instance = new Singleton(); //Explicit static consturctor to tell C# compiler //not to mark type as beforefieldinit static Singleton() { } private Singleton() { } public static Singleton Instance { get { return instance; } } }
この実装は非常にシンプルでロックを使用しませんが、依然としてスレッドセーフです。ここでは、静的な読み取り専用のシングルトン インスタンスが使用されます。シングルトンが初めて呼び出されたときに、新しいインスタンスが作成されます。新しいインスタンスを作成するときのスレッドの安全性の保証は、.NET によって直接制御されます。また、AppDomaining 内で 1 回だけ作成されます。
この実装にはいくつかの欠点もあります:
1 インスタンスが作成されるタイミングは不明であり、Singleton への呼び出しにより事前にインスタンスが作成されます2 静的コンストラクターの循環呼び出し。 2 つのクラス A と B があり、A の静的コンストラクターが B を呼び出し、B の静的コンストラクターが A を呼び出す場合、これら 2 つは循環呼び出しを形成し、プログラムが重大なクラッシュを引き起こす可能性があります。
3 Singleton の静的コンストラクターを手動で追加して、Singleton 型が beforefieldinit 属性で自動的に追加されないようにして、Singleton が初めて呼び出されたときにインスタンスが作成されるようにする必要があります。4readonly 属性は実行時に変更できません。プログラムの実行中にインスタンスを破棄して新しいインスタンスを再作成する必要がある場合、この実装方法は満たされません。
5 つの完全な遅延インスタンス化
public sealed class Singleton { private Singleton() { } public static Singleton Instance { get { return Nested.instance; } } private class Nested { // Explicit static constructor to tell C# compiler // not to mark type as beforefieldinit static Nested() { } internal static readonly Singleton instance = new Singleton(); } }
Six は .NET4 の Lazypublic sealed class Singleton
{
private static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton());
public static Singleton Instance
{
get
{
return lazy.Value;
}
}
private Singleton()
{
}
}
パフォーマンスの違い
前の実装では、スレッドの安全性とコードの遅延読み込みを強調しました。ただし、実際の使用では、シングルトン クラスの初期化に時間がかからない場合、または初期化シーケンスによってバグが発生しない場合、初期化にかかる時間は無視できるため、遅延初期化は必要ありません。
実際の使用シナリオでは、シングルトン インスタンスが頻繁に呼び出される場合 (ループ内など)、スレッド セーフの確保によって生じるパフォーマンスの消費はより注目に値します。
これらの実装のパフォーマンスを比較するために、これらの実装のシングルトンを 9 億回ループし、毎回インスタンス メソッドを呼び出して count++ 操作を実行し、100 万ごとに出力して、環境を実行するという小さなテストを作成しました。 MBP 上の Visual Studio for Mac です。結果は次のとおりです。
スレッド セーフティ | 遅延読み込み | テスト実行時間 (ミリ秒) | |
---|---|---|---|
実装 1 | いいえ | はい | 15532 |
実績2 | は | は | 45803 |
3を実現する | は | は | 15953 |
4を実現する | 完全ではありません | 14572 | |
ははいはい | 14295が6つの | yes | yes |
テスト方法は厳密ではありませんが、メソッド2が最も時間がかかるため、それは最も時間がかかることがわかります。毎回 lock を呼び出す必要があり、他のものよりもほぼ 3 倍多くなります。 2 位は .NET Lazy 型を使用した実装で、他の実装より約 2 分の 1 多くなっています。残りの 4 つは明らかな違いはありません。 | 概要 |
引用
この記事の大部分は、C# でのシングルトン パターンの実装を翻訳したものであり、私自身の理解もいくつか追加されています。これは、静的読み取り専用フィールドの初期化と静的コンストラクターの初期化を検索したときに見つけたものです。ここで 2 人の著者に感謝の意を表したいと思います。以上がC#シングルトンパターンの実装と性能比較例の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。