थ्रेड-सुरक्षित शब्दकोश को लागू करने का सबसे अच्छा तरीका क्या है?


109

मैं IDEDIA से प्राप्त करके और एक निजी SyncRoot ऑब्जेक्ट को परिभाषित करके C # में एक थ्रेड-सुरक्षित शब्दकोश को लागू करने में सक्षम था:

public class SafeDictionary<TKey, TValue>: IDictionary<TKey, TValue>
{
    private readonly object syncRoot = new object();
    private Dictionary<TKey, TValue> d = new Dictionary<TKey, TValue>();

    public object SyncRoot
    {
        get { return syncRoot; }
    } 

    public void Add(TKey key, TValue value)
    {
        lock (syncRoot)
        {
            d.Add(key, value);
        }
    }

    // more IDictionary members...
}

मैं तब अपने उपभोक्ताओं (कई थ्रेड्स) पर इस SyncRoot ऑब्जेक्ट पर लॉक करता हूं:

उदाहरण:

lock (m_MySharedDictionary.SyncRoot)
{
    m_MySharedDictionary.Add(...);
}

मैं इसे काम करने में सक्षम था, लेकिन इसके परिणामस्वरूप कुछ बदसूरत कोड थे। मेरा प्रश्न यह है कि क्या थ्रेड-सेफ डिक्शनरी को लागू करने का एक बेहतर, अधिक सुरुचिपूर्ण तरीका है?


3
यह आप इसके बारे में बदसूरत क्या है?
आसफ आर

1
मुझे लगता है कि वह शेर्डबेडर वर्ग के उपभोक्ताओं के भीतर अपने पूरे कोड के सभी लॉक स्टेटमेंट्स का जिक्र कर रहा है - वह कॉलिंग कोड में हर बार साझा कर रहा है, वह एक शेयरड्रेस ऑब्जेक्ट पर एक विधि का उपयोग कर रहा है।
पीटर मेयर

ऐड-मेथड का उपयोग करने के बजाय, मानों को पूर्व-m_MySedenDictionary ["key1"] = "item1" असाइन करके देखें, यह थ्रेड सुरक्षित है।
वृषण

जवाबों:


43

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

public class SafeDictionary<TKey, TValue>: IDictionary<TKey, TValue>
{
    private readonly object syncRoot = new object();
    private Dictionary<TKey, TValue> d = new Dictionary<TKey, TValue>();

    public void Add(TKey key, TValue value)
    {
        lock (syncRoot)
        {
            d.Add(key, value);
        }
        OnItemAdded(EventArgs.Empty);
    }

    public event EventHandler ItemAdded;

    protected virtual void OnItemAdded(EventArgs e)
    {
        EventHandler handler = ItemAdded;
        if (handler != null)
            handler(this, e);
    }

    // more IDictionary members...
}

संपादित करें: MSDN डॉक्स इंगित करता है कि गणना करना स्वाभाविक रूप से थ्रेड सुरक्षित नहीं है। यह आपकी कक्षा के बाहर एक सिंक्रनाइज़ेशन ऑब्जेक्ट को उजागर करने का एक कारण हो सकता है। एक और तरीका है कि सभी सदस्यों पर कार्रवाई करने के लिए कुछ तरीके प्रदान करने और सदस्यों की गणना करने के लिए चारों ओर ताला लगा दिया जाएगा। इसके साथ समस्या यह है कि आप यह नहीं जानते हैं कि उस फ़ंक्शन को दी गई कार्रवाई आपके शब्दकोश के कुछ सदस्य को बुलाती है (जिसके परिणामस्वरूप गतिरोध होगा)। सिंक्रोनाइज़ेशन ऑब्जेक्ट को एक्सपोज़ करने से उपभोक्ता को वे निर्णय लेने की अनुमति मिलती है और वह आपकी कक्षा के अंदर के गतिरोध को छिपा नहीं पाता है।


@fryguybob: एन्यूमरेशन वास्तव में एकमात्र कारण था जिसके कारण मैं सिंक्रोनाइज़ेशन ऑब्जेक्ट को उजागर कर रहा था। अधिवेशन द्वारा, मैं उस वस्तु पर केवल तभी ताला लगाऊंगा जब मैं संग्रह के माध्यम से गणना कर रहा हूं।
जी.पी.

1
यदि आपका शब्दकोश बहुत बड़ा नहीं है, तो आप एक कॉपी पर गणना कर सकते हैं और इसे कक्षा में बनाया जा सकता है।
०२ पर fryguybob

2
मेरा शब्दकोश बहुत बड़ा नहीं है, और मुझे लगता है कि इस चाल ने किया। मैंने जो किया वह एक नया सार्वजनिक तरीका था जिसे कॉपीफॉरनम () कहा जाता है, जो एक शब्दकोश का नया उदाहरण देता है जिसमें निजी शब्दकोश की प्रतियां होती हैं। इस पद्धति को तब एनुमरेशन के लिए बुलाया गया था, और सिंकरूट को हटा दिया गया था। धन्यवाद!
जी.पी.

12
यह भी एक स्वाभाविक रूप से थ्रेडसेफ़ क्लास नहीं है क्योंकि शब्दकोश संचालन दानेदार होते हैं। की तर्ज पर एक छोटा सा तर्क अगर (तानाशाही) (जो भी हो) {तानाशाही। (जो भी हो); तानाशाही। } निश्चित रूप से एक दौड़ की स्थिति होने की प्रतीक्षा कर रहा है।
प्लिंथ

207

.NET 4.0 वर्ग जो समवर्ती का समर्थन करता है उसका नाम है ConcurrentDictionary


4
कृपया इसे प्रतिक्रिया के रूप में चिह्नित करें, आपको कस्टम तानाशाही की आवश्यकता नहीं है अगर स्वयं के पास .Net का एक समाधान है
अल्बर्टो लेओन

27
(याद रखें कि अन्य उत्तर .NET 4.0 (2010 में जारी) के अस्तित्व से बहुत पहले लिखा गया था।)
जेपी स्टिग नीलसन

1
दुर्भाग्य से यह लॉक-फ्री समाधान नहीं है, इसलिए यह SQL सर्वर CLR सुरक्षित असेंबलियों में बेकार है। आपको यहां बताए गए कुछ चीज़ों की आवश्यकता होगी: cse.chalmers.se/~tsigas/papers/Lock-Free_Dictionary.pdf या शायद यह कार्यान्वयन: github.com/hackcraft/Ariadne
Triynts

2
वास्तव में पुराना है, मुझे पता है, लेकिन यह ध्यान रखना महत्वपूर्ण है कि समवर्ती बनाम एक शब्दकोश का उपयोग करने से महत्वपूर्ण प्रदर्शन हानि हो सकती है। यह सबसे महंगी संदर्भ स्विचिंग का परिणाम है, इसलिए निश्चित करें कि किसी एक का उपयोग करने से पहले आपको थ्रेड-सुरक्षित शब्दकोश की आवश्यकता है।
outbred

60

आंतरिक रूप से सिंक्रनाइज़ करने का प्रयास लगभग निश्चित रूप से अपर्याप्त होगा क्योंकि यह अमूर्तता के बहुत कम स्तर पर है। कहते हैं कि आप Addऔर ContainsKeyसंचालन को व्यक्तिगत रूप से थ्रेड-सेफ बनाते हैं:

public void Add(TKey key, TValue value)
{
    lock (this.syncRoot)
    {
        this.innerDictionary.Add(key, value);
    }
}

public bool ContainsKey(TKey key)
{
    lock (this.syncRoot)
    {
        return this.innerDictionary.ContainsKey(key);
    }
}

तब क्या होता है जब आप कई धागों से इस कथित थ्रेड-सेफ बिट कोड को कहते हैं? क्या यह हमेशा ठीक रहेगा?

if (!mySafeDictionary.ContainsKey(someKey))
{
    mySafeDictionary.Add(someKey, someValue);
}

सीधा - सा जवाब है 'नहीं'। कुछ बिंदु परAdd विधि एक अपवाद को दर्शाती है कि कुंजी पहले से ही शब्दकोश में मौजूद है। यह थ्रेड-सेफ डिक्शनरी के साथ कैसे हो सकता है, आप पूछ सकते हैं? अच्छी तरह से सिर्फ इसलिए कि प्रत्येक ऑपरेशन थ्रेड-सुरक्षित है, दो ऑपरेशनों का संयोजन नहीं है, क्योंकि एक और थ्रेड इसे आपके कॉल ContainsKeyऔर के बीच संशोधित कर सकता है Add

इसका मतलब है कि इस प्रकार के परिदृश्य को सही ढंग से लिखने के लिए आपको बाहर ताला चाहिए शब्दकोश के , जैसे

lock (mySafeDictionary)
{
    if (!mySafeDictionary.ContainsKey(someKey))
    {
        mySafeDictionary.Add(someKey, someValue);
    }
}

लेकिन अब, जैसा कि आप बाहरी रूप से लॉकिंग कोड लिखने के लिए देख रहे हैं, आप आंतरिक और बाहरी सिंक्रनाइज़ेशन को मिला रहे हैं, जिससे हमेशा अस्पष्ट कोड और गतिरोध जैसी समस्याएं होती हैं। तो आखिरकार आप या तो बेहतर हो सकते हैं:

  1. एक सामान्य का उपयोग करें Dictionary<TKey, TValue> और बाहरी रूप से सिंक्रनाइज़ करें, इस पर यौगिक संचालन को संलग्न करें, या

  2. एक अलग इंटरफ़ेस (यानी नहीं IDictionary<T>) के साथ एक नया थ्रेड-सुरक्षित आवरण लिखें, जो ऑपरेशन जैसे ए को जोड़ती हैAddIfNotContained विधि के ताकि आपको इसके साथ संचालन को संयोजित करने की आवश्यकता न हो।

(मैं खुद # 1 के साथ जाना चाहता हूं)


10
यह इंगित करने योग्य है कि .NET 4.0 में संग्रह और शब्दकोशों जैसे थ्रेड-सुरक्षित कंटेनरों का एक पूरा समूह शामिल होगा, जिनका मानक संग्रह के लिए एक अलग इंटरफ़ेस है (यानी वे आपके लिए ऊपर विकल्प 2 कर रहे हैं)।
ग्रेग बीच

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

6

आपको अपनी निजी लॉक ऑब्जेक्ट को किसी प्रॉपर्टी के माध्यम से प्रकाशित नहीं करना चाहिए। लॉक ऑब्जेक्ट एक निजी बिंदु के रूप में अभिनय के एकमात्र उद्देश्य के लिए निजी रूप से मौजूद होना चाहिए।

यदि मानक मानक लॉक का उपयोग करके प्रदर्शन खराब साबित होता है, तो Wintellect का पावर थ्रेडिंग संग्रह बहुत उपयोगी हो सकता है।


5

कार्यान्वयन विधि के साथ कई समस्याएं हैं जिनका आप वर्णन कर रहे हैं।

  1. आपको कभी भी अपनी सिंक्रनाइज़ेशन ऑब्जेक्ट को उजागर नहीं करना चाहिए। ऐसा करने से वस्तु को हथियाने वाले उपभोक्ता पर अपने आप ही खुल जाएगा और उस पर ताला लग जाएगा और फिर आप टोस्ट कर सकते हैं।
  2. आप थ्रेड सुरक्षित वर्ग के साथ एक गैर-थ्रेड सुरक्षित इंटरफ़ेस लागू कर रहे हैं। IMHO यह आपको सड़क के नीचे खर्च करेगा

व्यक्तिगत रूप से, मैंने एक थ्रेड सुरक्षित वर्ग को लागू करने का सबसे अच्छा तरीका पाया है जो अपरिवर्तनीयता के माध्यम से है। यह वास्तव में समस्याओं की संख्या को कम करता है जिन्हें आप थ्रेड सुरक्षा के साथ चला सकते हैं। की जाँच करें एरिक Lippert ब्लॉग अधिक जानकारी के लिए।


3

आपको अपने उपभोक्ता वस्तुओं में SyncRoot प्रॉपर्टी को लॉक करने की आवश्यकता नहीं है। शब्दकोश के तरीकों के भीतर आपके पास ताला पर्याप्त है।

विस्तृत करने के लिए: जो कुछ हो रहा है, वह यह है कि आपका शब्दकोश लंबे समय तक बंद रहता है।

आपके मामले में क्या होता है निम्नलिखित हैं:

थ्रेड ए पहले से सिंकरूट पर लॉक प्राप्त करता है कि कॉल करने Sync_oot m_mySaredिरोड पर क्लिक करें। जोड़ें। थ्रेड बी फिर ताला प्राप्त करने का प्रयास करता है लेकिन अवरुद्ध है। वास्तव में, अन्य सभी धागे अवरुद्ध हैं। थ्रेड ए को ऐड विधि में कॉल करने की अनुमति है। ऐड विधि के भीतर लॉक स्टेटमेंट पर, थ्रेड ए को फिर से लॉक प्राप्त करने की अनुमति है क्योंकि यह पहले से ही इसका मालिक है। विधि के भीतर और फिर विधि के बाहर ताला संदर्भ से बाहर निकलने पर, थ्रेड ए ने सभी ताले जारी किए हैं जो अन्य थ्रेड्स को जारी रखने की अनुमति देते हैं।

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


1
सच नहीं है ... यदि आप आंतरिक रूप से थ्रेड-सेफ़ के बाद दो ऑपरेशन करते हैं, तो इसका मतलब यह नहीं है कि समग्र कोड ब्लॉक थ्रेड सुरक्षित है। उदाहरण के लिए: if ((myDict.ContainsKey (someKey)) {myDict.Add (someKey, someValue); } थ्रेडसेफ़ नहीं होगा, यहां तक ​​कि इसमें कॉन्टेन्सके और ऐड थ्रेडसेफ़ हैं
टिम

आपकी बात सही है, लेकिन मेरे जवाब और सवाल के संदर्भ से बाहर है। यदि आप प्रश्न को देखते हैं, तो यह ContainsKey को कॉल करने के बारे में बात नहीं करता है, और न ही मेरा जवाब देता है। मेरा जवाब SyncRoot पर एक ताला प्राप्त करने को संदर्भित करता है जो मूल प्रश्न में उदाहरण में दिखाया गया है। लॉक स्टेटमेंट के संदर्भ में एक या अधिक थ्रेड-सुरक्षित संचालन वास्तव में सुरक्षित रूप से निष्पादित होंगे।
पीटर मेयर

मुझे लगता है कि अगर वह कभी भी कर रहा है तो शब्दकोश में जोड़ रहा है, लेकिन चूंकि उसके पास "// अधिक IDEDIA सदस्य हैं ...", मुझे लगता है कि कुछ बिंदु पर वह भी शब्दकोश से डेटा वापस पढ़ना चाहता है। यदि ऐसा है, तो बाहरी रूप से सुलभ लॉकिंग तंत्र होने की आवश्यकता है। इससे कोई फर्क नहीं पड़ता कि यह शब्दकोष में ही SyncRoot है या लॉकिंग के लिए पूरी तरह से उपयोग की गई कोई अन्य वस्तु है, लेकिन इस तरह की योजना के बिना, समग्र कोड थ्रेड-सुरक्षित नहीं होगा।
टिम

बाहरी लॉकिंग तंत्र है जैसा कि वह प्रश्न में अपने उदाहरण से पता चलता है: लॉक (m_MySeden गुर्जर। SynRoot) {m_MySaredDictionary.Add (...); } - यह निम्नलिखित करने के लिए पूरी तरह से सुरक्षित होगा: ताला (m_MySaredDictionary.SyncRoot) {if (! (m_MySared गुर्जर .Contains (...)) {m_MySaredDictionary.Add (...); }} दूसरे शब्दों में, बाहरी लॉकिंग तंत्र लॉक स्टेटमेंट है जो सार्वजनिक संपत्ति SyncRoot पर संचालित होता है।
पीटर मेयर

0

बस एक विचार क्यों शब्दकोश फिर से बनाना नहीं है? यदि पढ़ना लेखन की एक भीड़ है तो लॉकिंग सभी अनुरोधों को सिंक्रनाइज़ करेगा।

उदाहरण

    private static readonly object Lock = new object();
    private static Dictionary<string, string> _dict = new Dictionary<string, string>();

    private string Fetch(string key)
    {
        lock (Lock)
        {
            string returnValue;
            if (_dict.TryGetValue(key, out returnValue))
                return returnValue;

            returnValue = "find the new value";
            _dict = new Dictionary<string, string>(_dict) { { key, returnValue } };

            return returnValue;
        }
    }

    public string GetValue(key)
    {
        string returnValue;

        return _dict.TryGetValue(key, out returnValue)? returnValue : Fetch(key);
    }

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