क्या सी # स्टैटिक कंस्ट्रक्टर थ्रेड सुरक्षित है?


247

दूसरे शब्दों में, क्या यह सिंगलटन कार्यान्वयन धागा सुरक्षित है:

public class Singleton
{
    private static Singleton instance;

    private Singleton() { }

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

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

1
यह धागा-सुरक्षित है। मान लें कि कई सूत्र Instanceएक ही बार में संपत्ति प्राप्त करना चाहते हैं । थ्रेड्स में से एक को पहले टाइप इनिशियलाइज़र (जिसे स्टैटिक कंस्ट्रक्टर के रूप में भी जाना जाता है) चलाना होगा। इस बीच अन्य सभी सूत्र Instanceसंपत्ति को पढ़ना चाहते हैं, जब तक कि टाइप इनिशियलाइज़र समाप्त नहीं हो जाता है तब तक लॉक रहेगा। फ़ील्ड इनिशियलाइज़र के समापन के बाद ही, थ्रेड्स को Instanceमूल्य प्राप्त करने की अनुमति दी जाएगी । तो कोई भी Instanceहोने को नहीं देख सकता null
जेपी स्टिग नील्सन

@JeppeStigNielsen अन्य थ्रेड्स लॉक नहीं हैं। अपने स्वयं के अनुभव से मुझे उस के कारण गलतियाँ मिलीं। गारंटी यह है कि केवल मुट्ठी धागा ही स्टैटिक इनिशियलाइज़र, या कंस्ट्रक्टर को शुरू करेगा, लेकिन फिर अन्य थ्रेड्स स्टैटिक विधि का उपयोग करने की कोशिश करेंगे, भले ही निर्माण प्रक्रिया पूरी न हुई हो।
Narvalex

2
@Narvalex यह नमूना कार्यक्रम (URL में इनकोड किया गया स्रोत) आपके द्वारा बताई गई समस्या को पुन: उत्पन्न नहीं कर सकता है। शायद यह इस बात पर निर्भर करता है कि आपके पास सीएलआर का कौन सा संस्करण है?
जेपी स्टिग नीलसन

@JeppeStigNielsen अपना समय निकालने के लिए धन्यवाद। क्या आप मुझे समझा सकते हैं कि यहाँ पर मैदान क्यों उखाड़ा गया है?
Narvalex

5
@Narvalex उस कोड के साथ, अपर-केस बिना थ्रेडिंग के भीX समाप्त होता है । यह एक धागा-सुरक्षा मुद्दा नहीं है। इसके बजाय, इनिशलाइज़र पहले चलता है (यह कोड में एक पुरानी लाइन पर है, एक निचली लाइन संख्या है)। फिर इनिलाइज़र चलता है, जो ऊपरी-मामले को बराबर बनाता है । और फिर "स्पष्ट" स्टेटिक कंस्ट्रक्टर, टाइप इनिशलाइज़र चलता है, जो केवल लोअर-केस बदलता है । तो उस सब के बाद, विधि (या विधि) पर जाकर ऊपरी मामले को पढ़ा जा सकता है । इसका मूल्य सिर्फ एक धागे से भी होगा । -1 x = -1X = GetX()X-1static C() { ... }xMainOtherX-1
जेपी स्टिग नील्सन

जवाबों:


189

स्टेटिक कंस्ट्रक्टरों को केवल एक बार एप्लिकेशन डोमेन के अनुसार चलने की गारंटी दी जाती है, इससे पहले कि किसी वर्ग के इंस्टेंसेस बनाए जाएं या किसी भी स्थिर सदस्यों तक पहुँचा जाए। https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-constructors

दिखाए गए कार्यान्वयन प्रारंभिक निर्माण के लिए थ्रेड सुरक्षित है, अर्थात, सिंगलटन ऑब्जेक्ट के निर्माण के लिए कोई लॉकिंग या नल परीक्षण आवश्यक नहीं है। हालांकि, इसका मतलब यह नहीं है कि उदाहरण का कोई भी उपयोग सिंक्रनाइज़ किया जाएगा। इसके लिए कई तरह के उपाय किए जा सकते हैं; मैंने नीचे एक दिखाया है।

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 () को कॉल करने की उम्मीद है जब यह सिंगलटन उदाहरण का उपयोग करके किया जाता है। ऐसा करने में विफल (उदाहरण के लिए समय से पहले लौटने, अपवाद के माध्यम से गुंजाइश छोड़ने, कॉल को भूल जाना), अगली बार जब इस सिंगलटन को एक अलग धागे से एक्सेस किया जाएगा तो यह सिंगलटन में समाप्त हो जाएगा।
मिलान गार्डियन

2
सहमत, हालांकि मैं और आगे जाऊँगा। यदि आपका सिंगलटन अपरिवर्तनीय है, तो सिंगलटन का उपयोग करना ओवरकिल है। बस स्थिरांक परिभाषित करें। अंत में, एक सिंगलटन का ठीक से उपयोग करने के लिए यह आवश्यक है कि डेवलपर्स को पता हो कि वे क्या कर रहे हैं। जैसा कि इस कार्यान्वयन के रूप में भंगुर है, यह अभी भी सवाल में एक से बेहतर है जहां उन त्रुटियों को स्पष्ट रूप से अप्रबंधित म्यूटेक्स के बजाय यादृच्छिक रूप से प्रकट होता है।
जोबा

26
रिलीज़ () विधि की भंगुरता को कम करने का एक तरीका यह है कि एक दूसरे वर्ग का उपयोग आईडी हैंडलर के साथ सिंक हैंडलर के रूप में किया जाए। जब आप सिंगलटन का अधिग्रहण करते हैं, तो आपको हैंडलर मिलता है और रिलीज को संभालने के लिए सिंगलटन को एक ब्लॉक का उपयोग करके कोड की आवश्यकता होती है।
कोडेक्सआर्कनम

5
अन्य लोगों के लिए जिन्हें इसके द्वारा जोड़ दिया जा सकता है: किसी भी स्थैतिक क्षेत्र के सदस्यों के साथ इनिशियलाइज़र्स को स्टैटिक कंस्ट्रक्टर कहे जाने से पहले आरंभीकृत किया जाता है।
एडम डब्ल्यू। मैकिनले

12
इन दिनों उत्तर का उपयोग करना है Lazy<T>- कोई भी व्यक्ति जो मेरे द्वारा पोस्ट किए गए कोड का उपयोग करता है वह गलत कर रहा है (और ईमानदारी से यह शुरू करने के लिए उतना अच्छा नहीं था - 5 साल पहले-मुझे इस सामान के रूप में वर्तमान में उतना अच्छा नहीं था -मुझे है :) )।
ज़ोबा

86

जबकि ये सभी उत्तर एक ही सामान्य उत्तर दे रहे हैं, एक प्रश्न है।

याद रखें कि एक सामान्य वर्ग के सभी संभावित व्युत्पन्न व्यक्तिगत प्रकारों के रूप में संकलित किए जाते हैं। तो जेनेरिक प्रकारों के लिए स्थैतिक निर्माणकर्ताओं को लागू करते समय सावधानी बरतें।

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

टाइपोफ़ (MyObject <T>)! = टाइपोफ़ (MyObject <Y>);
करीम आगा

6
मुझे लगता है कि यही वह बिंदु है जिसे मैं बनाने की कोशिश कर रहा हूं। जेनेरिक प्रकारों को व्यक्तिगत प्रकारों के रूप में संकलित किया जाता है, जिसके आधार पर जेनेरिक मापदंडों का उपयोग किया जाता है, इसलिए स्थिर निर्माणकर्ता को कई बार कहा जा सकता है।
ब्रायन रुडोल्फ

1
यह सही है जब T मान प्रकार का होता है, संदर्भ प्रकार T के लिए केवल एक सामान्य प्रकार उत्पन्न होगा
sll

2
@sll: सच नहीं है ... मेरा संपादन देखें
ब्रायन रुडोल्फ

2
सभी प्रकारों के लिए दिलचस्प लेकिन वास्तव में स्थिर कॉशनट्रक्टर, केवल कई संदर्भ प्रकारों के लिए कोशिश की गई
sll

28

एक स्थिर कंस्ट्रक्टर का उपयोग करना वास्तव में थ्रेडसेफ़ है। स्थिर कंस्ट्रक्टर को केवल एक बार निष्पादित करने की गारंटी है।

C # भाषा विनिर्देश से :

किसी वर्ग के लिए स्थिर कंस्ट्रक्टर किसी दिए गए एप्लिकेशन डोमेन में एक बार में निष्पादित होता है। स्थैतिक निर्माणकर्ता का निष्पादन एक आवेदन डोमेन के भीतर होने वाली निम्न घटनाओं में से पहले से शुरू होता है:

  • वर्ग का एक उदाहरण बनाया जाता है।
  • वर्ग के किसी भी स्थिर सदस्य को संदर्भित किया जाता है।

तो हां, आप भरोसा कर सकते हैं कि आपका सिंगलटन तुरंत सही हो जाएगा।

ज़ोबा ने एक उत्कृष्ट बिंदु बनाया (और मेरे सामने 15 सेकंड भी!) कि स्थिर निर्माणकर्ता सिंगलटन को थ्रेड-सुरक्षित साझा पहुंच की गारंटी नहीं देगा। जिसे दूसरे तरीके से संभालने की जरूरत होगी।


8

यहाँ सी # सिंगलटन पर उपरोक्त MSDN पृष्ठ से क्लिफनोट्स संस्करण है:

निम्न पैटर्न का उपयोग करें, हमेशा, आप गलत नहीं हो सकते हैं:

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

   private Singleton(){}

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

स्पष्ट सिंगलटन विशेषताओं से परे, यह आपको मुफ्त में ये दो चीजें देता है (सी ++ में सिंगलटन के संबंध में):

  1. आलसी निर्माण (या कोई निर्माण नहीं अगर इसे कभी नहीं कहा जाता)
  2. तादात्म्य

3
आलसी यदि वर्ग में कोई अन्य असंबंधित स्टैटिक्स (जैसे कास्ट) नहीं है। अन्यथा किसी भी स्थैतिक विधि या संपत्ति तक पहुँचने से परिणाम सृजन होगा। इसलिए मैं इसे आलसी नहीं कहूंगा।
Schultz9999

6

स्टेटिक कंस्ट्रक्टरों को केवल एक बार ऐप डोमेन के अनुसार आग लगाने की गारंटी दी जाती है, इसलिए आपका दृष्टिकोण ठीक होना चाहिए। हालाँकि, यह कार्यात्मक रूप से अधिक संक्षिप्त, इनलाइन संस्करण से अलग नहीं है:

private static readonly Singleton instance = new Singleton();

थ्रेड सुरक्षा एक समस्या का अधिक है जब आप आलसी चीजों को शुरू कर रहे हैं।


4
एंड्रयू, यह पूरी तरह से बराबर नहीं है। स्टैटिक कंस्ट्रक्टर का उपयोग नहीं करने से, इनिशियलाइज़र के निष्पादन के बारे में कुछ गारंटियाँ खो जाती हैं। कृपया में गहराई से स्पष्टीकरण के लिए इन लिंक देखें: * < csharpindepth.com/Articles/General/Beforefieldinit.aspx > * < ondotnet.com/pub/a/dotnet/2003/07/07/staticxtor.html >
डेरेक पार्क

डेरेक, मैं पहले के क्षेत्र "अनुकूलन" से परिचित हूं लेकिन, व्यक्तिगत रूप से, मैं इसके बारे में कभी चिंता नहीं करता।
एंड्रयू पीटर्स

के लिए @ 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।) इसलिए जब तक आप सिंगलटन सिंगलटन कॉलिंग पर कुछ अजीब इल्म नहीं रखते हैं ::। Cctor सीधे (बिना किसी संभावना के) आपका स्टैटिक कंस्ट्रक्टर सिंग्लटन प्रकार का उपयोग करने से पहले ठीक एक बार चलेगा, केवल सिंगलटन का एक उदाहरण बनाया जाएगा। और आपकी इंस्टेंस प्रॉपर्टी थ्रेड-सेफ है।

ध्यान दें कि यदि सिंगलटन के निर्माता इंस्टेंस प्रॉपर्टी (यहां तक ​​कि अप्रत्यक्ष रूप से) का उपयोग करते हैं तो इंस्टेंस प्रॉपर्टी अशक्त होगी। सबसे अच्छा आप यह पता लगा सकते हैं कि ऐसा कब होता है और एक अपवाद को फेंक दें, इस बात की जांच करके कि संपत्ति के एक्सेसर में गैर-अशक्त है। आपके स्थिर कंस्ट्रक्टर के पूरा होने के बाद Instance की संपत्ति गैर-शून्य हो जाएगी।

जैसा कि ज़ोम्बा का जवाब बताता है कि आपको सिंगलटन को कई थ्रेड्स से एक्सेस करने के लिए सुरक्षित करना होगा, या सिंगलटन इंस्टेंस का उपयोग करके चारों ओर एक लॉकिंग मैकेनिज्म को लागू करना होगा।


2

सिर्फ पांडित्यपूर्ण होने के लिए, लेकिन एक स्थिर निर्माता के रूप में ऐसी कोई चीज नहीं है, बल्कि स्थिर प्रकार के इनिशियलाइज़र हैं, यहाँ चक्रीय स्थिर कंस्ट्रक्टर निर्भरता का एक छोटा डेमो है जो इस बिंदु को दिखाता है।


1
Microsoft असहमत लगता है। msdn.microsoft.com/en-us/library/k9x6w0hc.aspx
Westy92


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

मूल लेखक इगोर ओस्ट्रोव्स्की हैं, यहां उनकी पोस्ट देखें ।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.