C#静的コンストラクターはスレッドセーフですか?


247

つまり、このシングルトン実装はスレッドセーフですか。

public class Singleton
{
    private static Singleton instance;

    private Singleton() { }

    static Singleton()
    {
        instance = new Singleton();
    }

    public static Singleton Instance
    {
        get { return instance; }
    }
}

1
スレッドセーフです。複数のスレッドがInstance一度にプロパティを取得したいとします。スレッドの1つは、型初期化子(静的コンストラクターとも呼ばれる)を最初に実行するように指示されます。その間、Instanceプロパティを読み取ろうとする他のすべてのスレッドは、型初期化子が完了するまでロックされます。フィールド初期化子が終了した後にのみ、スレッドはInstance値を取得できます。だから誰も存在を見るInstanceことができませんnull
Jeppe Stig Nielsen

@JeppeStigNielsen他のスレッドはロックされていません。私自身の経験から、そのために厄介なエラーが発生しました。保証は、最初のスレッドだけが静的イニシャライザまたはコンストラクタを開始し、その後、構築プロセスが完了していなくても、他のスレッドが静的メソッドを使用しようとすることです。
Narvalex

2
@Narvalex このサンプルプログラム(URLでエンコードされたソース)は、記述された問題を再現できません。多分それはあなたが持っているCLRのどのバージョンに依存していますか?
Jeppe Stig Nielsen

@JeppeStigNielsenお時間を割いていただきありがとうございます。ここでフィールドがオーバーライドされる理由を教えてください。
-Narvalex

5
@Narvalexそのコードでは、大文字は最終的にスレッド化Xされ-1 ていなくもなります。スレッドセーフの問題ではありません。代わりに、初期化子x = -1が最初に実行されます(コードの前の行にあり、行番号が小さい)。次に、イニシャライザX = GetX()が実行され、大文字がにX等しくなり-1ます。次に、「明示的な」静的コンストラクターである型初期化子static C() { ... }が実行され、小文字のみが変更されxます。そのため、すべてのMainメソッド(またはOtherメソッド)は続けて大文字を読み取ることができXます。-1スレッドが1つでも、その値はになります。
Jeppe Stig Nielsen

回答:


189

静的コンストラクターは、クラスのインスタンスが作成されるか、静的メンバーにアクセスされる前に、アプリケーションドメインごとに1回だけ実行されることが保証されています。https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-constructors

示されている実装は、初期構築ではスレッドセーフです。つまり、Singletonオブジェクトを構築するためにロックやnullテストは必要ありません。ただし、これはインスタンスの使用が同期されることを意味するものではありません。これを行うにはさまざまな方法があります。以下に示します。

public class Singleton
{
    private static Singleton instance;
    // Added a static mutex for synchronising use of instance.
    private static System.Threading.Mutex mutex;
    private Singleton() { }
    static Singleton()
    {
        instance = new Singleton();
        mutex = new System.Threading.Mutex();
    }

    public static Singleton Acquire()
    {
        mutex.WaitOne();
        return instance;
    }

    // Each call to Acquire() requires a call to Release()
    public static void Release()
    {
        mutex.ReleaseMutex();
    }
}

53
シングルトンオブジェクトが不変である場合、ミューテックスまたは同期メカニズムの使用は過剰であり、使用すべきではないことに注意してください。また、上記のサンプル実装は非常に壊れやすいと思います:-)。Singleton.Acquire()を使用するすべてのコードは、シングルトンインスタンスを使用して完了したときに、Singleton.Release()を呼び出すことが期待されています。これに失敗すると(たとえば、途中で戻り、例外を介してスコープを離れ、Releaseの呼び出しを忘れる)、このシングルトンが別のスレッドからアクセスされると、Singleton.Acquire()でデッドロックします。
ミラノ

2
同意しますが、さらに進めます。シングルトンが不変である場合、シングルトンを使用するのはやりすぎです。定数を定義するだけです。最終的に、シングルトンを適切に使用するには、開発者が何をしているかを知る必要があります。この実装のように壊れやすいのは、明らかに解放されていないミューテックスとしてではなく、それらのエラーがランダムに現れる問題の問題よりも優れています。
Zooba

26
Release()メソッドの脆弱性を軽減する1つの方法は、IDisposableを持つ別のクラスを同期ハンドラーとして使用することです。シングルトンを取得すると、ハンドラーを取得し、シングルトンを必要とするコードをusingブロックに配置してリリースを処理できます。
CodexArcanum 2010

5
これによってつまずく可能性がある他の人のために:初期化子を持つ静的フィールドメンバーは、静的コンストラクターが呼び出されるに初期化されます。
アダムW.マッキンリー2011

12
最近の答えは使用することですLazy<T>-私が最初に投稿したコードを使用する人は誰でもそれを間違っています-meは:)です。
Zooba 2014

86

これらの答えはすべて同じ一般的な答えを示していますが、注意点が1つあります。

ジェネリッククラスのすべての潜在的な派生は、個別の型としてコンパイルされることに注意してください。そのため、ジェネリック型の静的コンストラクターを実装する場合は注意が必要です。

class MyObject<T>
{
    static MyObject() 
    {
       //this code will get executed for each T.
    }
}

編集:

ここにデモンストレーションがあります:

static void Main(string[] args)
{
    var obj = new Foo<object>();
    var obj2 = new Foo<string>();
}

public class Foo<T>
{
    static Foo()
    {
         System.Diagnostics.Debug.WriteLine(String.Format("Hit {0}", typeof(T).ToString()));        
    }
}

コンソールで:

Hit System.Object
Hit System.String

typeof(MyObject <T>)!= typeof(MyObject <Y>);
Karim Agha 2011年

6
それが私が作ろうとしているポイントだと思います。ジェネリック型は、使用されるジェネリックパラメーターに基づいて個別の型としてコンパイルされるため、静的コンストラクターは複数回呼び出すことができます。
ブライアンルドルフ

1
これは、Tが値型の場合に適切です。参照型Tの場合、生成されるジェネリック型は1つだけです
sll

2
@sll:真実ではありません...私の編集を参照してください
ブライアンルドルフ

2
興味深いが、実際には静的なcosntructorがすべての型を呼び出し、複数の参照型を試しました
sll

28

静的コンストラクタの使用は実際にスレッドセーフです。静的コンストラクタは、1回だけ実行されることが保証されています。

C#言語仕様から

クラスの静的コンストラクタは、特定のアプリケーションドメインで最大1回実行されます。静的コンストラクターの実行は、アプリケーションドメイン内で発生する次の最初のイベントによってトリガーされます。

  • クラスのインスタンスが作成されます。
  • クラスの静的メンバーのいずれかが参照されている。

つまり、シングルトンが正しくインスタンス化されることを信頼できます。

Zoobaは、静的コンストラクターがシングルトンへのスレッドセーフな共有アクセスを保証しないという優れた点(そして15秒前にも!)を示しました。これは別の方法で処理する必要があります。


8

これは、c#シングルトンの上のMSDNページのCliffnotesバージョンです。

常に次のパターンを使用してください。間違いはありません。

public sealed class Singleton
{
   private static readonly Singleton instance = new Singleton();

   private Singleton(){}

   public static Singleton Instance
   {
      get 
      {
         return instance; 
      }
   }
}

明白なシングルトン機能を超えて、それはあなたにこれらの2つのものを無料で提供します(C ++のシングルトンに関して):

  1. 遅延構築(または呼び出されなかった場合は構築なし)
  2. 同期

3
クラスが他の無関係な静的要素(constなど)を持たない場合は遅延します。そうでない場合、静的メソッドまたはプロパティにアクセスすると、インスタンスが作成されます。だから私はそれを怠惰とは言いません。
Schultz9999 2013年

6

静的コンストラクターはアプリドメインごとに1回だけ起動することが保証されているため、アプローチは問題ありません。ただし、機能的には、より簡潔なインラインバージョンと同じです。

private static readonly Singleton instance = new Singleton();

スレッドの安全性は、遅延して初期化する場合の問題です。


4
アンドリュー、それは完全に同等ではありません。静的コンストラクタを使用しないと、初期化子がいつ実行されるかについての保証の一部が失われます。詳細な説明については、次のリンクを参照してください。* < csharpindepth.com/Articles/General/Beforefieldinit.aspx > * < ondotnet.com/pub/a/dotnet/2003/07/07/staticxtor.html >
Derek Park

デレク、私はbeforefieldinitの「最適化」に精通していますが、個人的には心配していません。
アンドリューピーターズ

@DerekParkのコメントの作業用リンク:csharpindepth.com/Articles/General/Beforefieldinit.aspx。このリンクは古くなっているようです:ondotnet.com/pub/a/dotnet/2003/07/07/staticxtor.html
phoog

4

静的コンストラクターは、スレッドがクラスにアクセスできるようになる前に実行を終了します。

    private class InitializerTest
    {
        static private int _x;
        static public string Status()
        {
            return "_x = " + _x;
        }
        static InitializerTest()
        {
            System.Diagnostics.Debug.WriteLine("InitializerTest() starting.");
            _x = 1;
            Thread.Sleep(3000);
            _x = 2;
            System.Diagnostics.Debug.WriteLine("InitializerTest() finished.");
        }
    }

    private void ClassInitializerInThread()
    {
        System.Diagnostics.Debug.WriteLine(Thread.CurrentThread.GetHashCode() + ": ClassInitializerInThread() starting.");
        string status = InitializerTest.Status();
        System.Diagnostics.Debug.WriteLine(Thread.CurrentThread.GetHashCode() + ": ClassInitializerInThread() status = " + status);
    }

    private void classInitializerButton_Click(object sender, EventArgs e)
    {
        new Thread(ClassInitializerInThread).Start();
        new Thread(ClassInitializerInThread).Start();
        new Thread(ClassInitializerInThread).Start();
    }

上記のコードは以下の結果を生成しました。

10: ClassInitializerInThread() starting.
11: ClassInitializerInThread() starting.
12: ClassInitializerInThread() starting.
InitializerTest() starting.
InitializerTest() finished.
11: ClassInitializerInThread() status = _x = 2
The thread 0x2650 has exited with code 0 (0x0).
10: ClassInitializerInThread() status = _x = 2
The thread 0x1f50 has exited with code 0 (0x0).
12: ClassInitializerInThread() status = _x = 2
The thread 0x73c has exited with code 0 (0x0).

静的コンストラクターの実行には長い時間がかかりましたが、他のスレッドは停止して待機しました。すべてのスレッドは、静的コンストラクターの下部で設定された_xの値を読み取ります。


3

共通言語基盤の仕様を保証する「明示的にユーザーコードによって呼び出さない限り、任意のタイプのために一度だけ実行しなければなら初期化子タイプ。」(セクション9.5.3.1。)したがって、緩やかに呼び出しているSingleton :: .. cctorにかなりのILが直接ない限り、静的コンストラクタは、Singleton型が使用される前に1回だけ実行され、Singletonのインスタンスが1つだけ作成されます。そして、Instanceプロパティはスレッドセーフです。

シングルトンのコンストラクターがInstanceプロパティに(間接的にも)アクセスする場合、Instanceプロパティはnullになることに注意してください。プロパティアクセサーでインスタンスがnull以外であることを確認することで、これが発生したことを検出して例外をスローすることが最善の方法です。静的コンストラクターが完了すると、Instanceプロパティはnull以外になります。

以下のようZoombaの答えをあなたポイントは、複数のスレッドからのアクセスにシングルトンの安全を作る、またはシングルトンインスタンスを使用して、周りのロック機構を実装する必要があります。




0

他の答えはほぼ正しいですが、静的コンストラクターにはさらに別の警告があります。

セクションII.10.5.3.3に従ってECMA-335共通言語インフラストラクチャの 競合とデッドロック

型の初期化だけでは、型の初期化子から(直接的または間接的に)呼び出されたコードが明示的にブロッキング操作を呼び出さない限り、デッドロックは発生しません。

次のコードはデッドロックになります

using System.Threading;
class MyClass
{
    static void Main() { /* Won’t run... the static constructor deadlocks */  }

    static MyClass()
    {
        Thread thread = new Thread(arg => { });
        thread.Start();
        thread.Join();
    }
}

原作者はイゴールオストロフスキーです。彼の投稿はこちらをご覧ください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.