C # statik yapıcı iş parçacığı güvenli midir?


247

Başka bir deyişle, bu Singleton uygulama iş parçacığı güvenli midir:

public class Singleton
{
    private static Singleton instance;

    private Singleton() { }

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

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

1
İplik korumalıdır. Birkaç iş parçacığının özelliği bir Instancekerede almak istediğinizi varsayalım . İpliklerden birine ilk olarak tip başlatıcısı (statik yapıcı olarak da bilinir) çalıştırması söylenecektir. Bu arada Instanceözelliği okumak isteyen diğer tüm evreler , tip başlatıcısı bitene kadar kilitlenir . Yalnızca alan başlatıcısı tamamlandıktan sonra, iş parçacıklarının değeri almasına izin verilir Instance. Böylece kimse Instancevarlığı göremez null.
Jeppe Stig Nielsen

@JeppeStigNielsen Diğer evreler kilitli değil. Kendi deneyimlerimden dolayı bu yüzden kötü hatalar aldım. Garanti, yalnızca yumruk iş parçacığının statik başlatıcıyı veya yapıcıyı başlatacağı, ancak daha sonra diğer iş parçacıkları, inşaat işlemi bitmemiş olsa bile statik bir yöntem kullanmaya çalışacaktır.
Narvalex

2
@Narvalex Bu örnek program (URL'de kodlanmış kaynak) açıkladığınız sorunu yeniden oluşturamaz. Belki de CLR'nin hangi sürümüne sahip olduğunuza bağlıdır?
Jeppe Stig Nielsen

@JeppeStigNielsen Zaman ayırdığınız için teşekkür ederiz. Bana buradaki alanın neden geçersiz olduğunu açıklayabilir misiniz ?
Narvalex

5
@Narvalex Bu kodla büyük harf , diş açmadan bileX son bulur . Bu bir iş parçacığı güvenliği sorunu değildir. Bunun yerine, başlatıcı ilk önce çalışır (kodun önceki bir satırında, daha düşük bir satır numarasındadır). Sonra başlatıcı çalışır, bu da büyük harf eşittir . Ve sonra "açık" statik yapıcı, sadece küçük harf değiştiren tür başlatıcısı çalışır . Bundan sonra, yöntem (veya yöntem) devam edebilir ve büyük harf okuyabilir . Değeri, sadece bir iş parçacığında bile olacaktır . -1 x = -1X = GetX()X-1static C() { ... }xMainOtherX-1
Jeppe Stig Nielsen

Yanıtlar:


189

Statik kurucuların, bir sınıfın herhangi bir örneği oluşturulmadan veya statik üyelere erişilmeden önce her uygulama etki alanı için yalnızca bir kez çalıştırılacağı garanti edilir. https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-constructors

Gösterilen uygulama, ilk konstrüksiyon için diş açmaya karşı güvenlidir, yani Singleton nesnesinin oluşturulması için herhangi bir kilitleme veya null test gerekli değildir. Ancak bu, örneğin herhangi bir kullanımının senkronize edileceği anlamına gelmez. Bunun yapılabileceği çeşitli yollar vardır; Aşağıda bir tane gösterdim.

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
Tekli nesneniz değiştirilemezse, bir muteks veya herhangi bir senkronizasyon mekanizması kullanmanın aşırı doldurma olduğunu ve kullanılmaması gerektiğini unutmayın. Ayrıca, yukarıdaki örnek uygulama son derece kırılgan bulabilirsiniz :-). Singleton örneği kullanılarak tamamlandığında, Singleton.Acquire () öğesini kullanan tüm kodların Singleton.Release () öğesini çağırması beklenir. Bunu yapamadığınızda (örn. Zamanından önce geri dönme, istisna yoluyla kapsamı terk etme, Release'yi aramayı unutma), bu Singleton'a farklı bir iş parçasından bir sonraki erişimde Singleton.Acquire () içinde kilitlenecektir.
Milan Gardian

2
Kabul ettim, daha ileri gidebilirdim. Eğer singletonunuz değişmezse, singleton kullanmak fazladır. Sadece sabitleri tanımlayın. Sonuçta, tek birtonun düzgün kullanılması geliştiricilerin ne yaptıklarını bilmelerini gerektirir. Bu uygulama kadar kırılgan olsa da, bu hataların açıkça yayınlanmamış bir muteks olmaktan ziyade rastgele ortaya çıktığı sorusundan daha iyidir.
Zooba

26
Release () yönteminin kırılganlığını azaltmanın bir yolu, eşitleme işleyicisi olarak IDisposable olan başka bir sınıf kullanmaktır. Singleton'u aldığınızda, işleyiciyi alırsınız ve singleton gerektiren kodu, sürümü serbest bırakmak için bir kullanma bloğuna koyabilirsiniz.
CodexArcanum

5
Bu durum tarafından tetiklenebilecek diğerleri için: Statik yapıcı çağrılmadan önce başlatıcıları olan statik alan üyeleri başlatılır .
Adam W. McKinley

12
Bu günlerde cevap kullanmak Lazy<T>- başlangıçta gönderdiğim kodu kullanan herkes yanlış yapıyor (ve dürüst olmak gerekirse başlamak için iyi değildi - 5 yıl önce-me bu şeylerde şu anki kadar iyi değildi -benim :)).
Zooba

86

Bütün bu cevaplar aynı genel cevabı verirken bir uyarı var.

Genel bir sınıfın tüm potansiyel türevlerinin bireysel türler olarak derlendiğini unutmayın. Bu nedenle, genel türler için statik yapıcılar uygularken dikkatli olun.

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

DÜZENLE:

İşte gösteri:

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()));        
    }
}

Konsolda:

Hit System.Object
Hit System.String

typeof (MyObject <T>)! = typeof (MyObject <Y>);
Karim Ağa

6
Sanırım yapmaya çalıştığım nokta bu. Genel türler, genel parametrelerin kullanıldığı bağımsız türler olarak derlenir, böylece statik yapıcı birden çok kez çağrılabilir ve çağrılabilir.
Brian Rudolph

1
Bu T değer tipinde olduğunda doğrudur, referans tip T için sadece bir genel tip üretilir
sll

2
@sll: Değil Gerçek ... Benim Düzen bakın
Brian Rudolph

2
İlginç ama gerçekten statik bir yapıcı tüm türleri çağırdı, sadece çoklu referans türleri için çalıştı
sll

28

Aslında statik yapıcı kullanılması olduğunu threadsafe. Statik kurucunun yalnızca bir kez yürütülmesi garanti edilir.

C # dil spesifikasyonundan :

Bir sınıfın statik yapıcısı, belirli bir uygulama etki alanında en fazla bir kez yürütülür. Statik bir kurucunun yürütülmesi, bir uygulama etki alanında gerçekleşen aşağıdaki olayların ilki tarafından tetiklenir:

  • Sınıfın bir örneği oluşturulur.
  • Sınıfın statik üyelerinden herhangi birine başvurulur.

Yani evet, singletonunuzun doğru bir şekilde başlatılacağına güvenebilirsiniz.

Zooba mükemmel bir noktaya değindi (ve benden 15 saniye önce!), Statik kurucunun singleton'a iş parçacığı için güvenli paylaşılan erişimi garanti etmeyeceği. Bunun başka bir şekilde ele alınması gerekecektir.


8

İşte c # singleton'ın yukarıdaki MSDN sayfasındaki Cliffnotes sürümü:

Aşağıdaki modeli kullanın, her zaman yanlış gidemezsiniz:

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

   private Singleton(){}

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

Belirgin singleton özelliklerinin ötesinde, bu iki şeyi ücretsiz olarak verir (c ++ 'daki singleton ile ilgili olarak):

  1. tembel inşaat (veya hiç çağrılmadıysa inşaat yok)
  2. senkronizasyon

3
Sınıfın temassız başka bir statiki (temalar gibi) yoksa tembel. Aksi takdirde, herhangi bir statik yönteme veya özelliğe erişmek örnek oluşturmaya neden olur. Bu yüzden tembel demem.
Schultz9999

6

Statik kurucuların her bir Uygulama Etki Alanı için yalnızca bir kez tetikleneceği garanti edilir, bu nedenle yaklaşımınız iyi olmalıdır. Ancak, işlevsel olarak daha özlü, satır içi sürümden farklı değildir:

private static readonly Singleton instance = new Singleton();

İşleri tembel olarak başlatırken iş parçacığı güvenliği daha önemli bir konudur.


4
Andrew, bu tamamen eşdeğer değil. Statik bir yapıcı kullanılmadığında, başlatıcı ne zaman yürütüleceğine dair bazı garantiler kaybolur. Ayrıntılı açıklama için lütfen bu bağlantılara bakın: * < csharpindepth.com/Articles/General/Beforefieldinit.aspx > * < ondotnet.com/pub/a/dotnet/2003/07/07/staticxtor.html >
Derek Park

Derek, daha önceki "optimizasyon" a aşinayım ama şahsen hiç endişe etmiyorum.
Andrew Peters

@ DerekPark'ın yorumu için çalışma linki: csharpindepth.com/Articles/General/Beforefieldinit.aspx . Bu bağlantı bayat gibi görünüyor: ondotnet.com/pub/a/dotnet/2003/07/07/staticxtor.html
phoog

4

Statik kurucu sınıfa erişmesine izin verilmeden önce çalışmayı bitirir .

    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();
    }

Yukarıdaki kod aşağıdaki sonuçları üretmiştir.

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).

Statik kurucunun çalışması uzun sürmesine rağmen, diğer dişler durdu ve bekledi. Tüm evreler statik kurucunun alt kısmında ayarlanan _x değerini okur.


3

Ortak Dil Altyapı şartname garanti olduğunu "açıkça kullanıcı koduyla adlandırılan sürece, herhangi bir tür için tam olarak bir kez çalıştırmak zorundadır başlatıcısı türüdür." (Kısım 9.5.3.1.) Bu nedenle, doğrudan çağrıda bulunan Singleton ::. Cctor üzerinde biraz zorlayıcı IL yoksa, statik kurucunuz Singleton türü kullanılmadan önce tam olarak bir kez çalışır, yalnızca bir Singleton örneği oluşturulur, ve Instance özelliğiniz iş parçacığı için güvenlidir.

Singleton yapıcısı Instance özelliğine (dolaylı olarak da olsa) erişirse, Instance özelliğinin null olacağını unutmayın. Yapabileceğiniz en iyi şey, bunun gerçekleştiğini algılamak ve özellik erişimcisinde bu örneğin null olmadığını kontrol ederek bir istisna atmaktır. Statik kurucunuz tamamlandıktan sonra Instance özelliği null olmayacaktır.

As Zoomba cevabı size Puan birden çok iş parçacığı erişim Singleton kasa yapmak veya tekil örneğini kullanarak etrafında bir kilitleme mekanizması uygulamak gerekir.




0

Diğer cevaplar çoğunlukla doğru olmasına rağmen, statik yapıcılarla başka bir uyarı daha vardır.

Bölüm gereğince II.10.5.3.3 Yarışları ve çözümsüzlüklerle ait ECMA-335 Ortak Dil Altyapısı

Bir tür başlatıcıdan (doğrudan veya dolaylı olarak) çağrılan bazı kodlar açıkça engelleme işlemlerini başlatmadığı sürece, yalnızca tür başlatma bir kilitlenme oluşturmaz.

Aşağıdaki kod bir kilitlenmeyle sonuçlanır

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();
    }
}

Orijinal yazar Igor Ostrovsky, yazısına bakın buradan .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.