कोई। समांतर सूची <T> में .net 4.0?


198

मैं System.Collections.Concurrent.net 4.0 में नए नाम स्थान को देखकर रोमांचित था , काफी अच्छा! मैंने देखा है ConcurrentDictionary, ConcurrentQueue, ConcurrentStack, ConcurrentBagऔर BlockingCollection

एक बात जो रहस्यमय ढंग से याद आ रही है वह है ए ConcurrentList<T>। क्या मुझे खुद लिखना है (या इसे वेब से हटाएं :))?

क्या में यहां कुछ भूल रहा हूँ?



4
@RodrigoReis, ConcurrentBag <T> एक अनियोजित संग्रह है, जबकि सूची <T> का आदेश दिया गया है।
एडम कैल्वेट बोहल

4
एक बहुस्तरीय वातावरण में संभवतः आपके पास एक आदेशित संग्रह कैसे हो सकता है? आप डिजाइन द्वारा तत्वों के अनुक्रम का नियंत्रण कभी नहीं होगा।
जेरेमी होलोवाक

इसके बजाय एक लॉक का उपयोग करें
एरिक बर्गस्टेड सेप

डॉटनेट सोर्स कोड में ThreadSafeList.cs नामक एक फाइल है जो नीचे कुछ कोड की तरह दिखती है। यह ReaderWriterLockSlim का भी उपयोग करता है और यह पता लगाने की कोशिश कर रहा था कि साधारण लॉक (obj) के बजाय इसका उपयोग क्यों करें?
कॉलिन लामरे

जवाबों:


166

मैंने इसे कुछ समय पहले भी दिया था (यह भी: गिटहब पर )। मेरे कार्यान्वयन में कुछ समस्याएं थीं, जो मुझे यहाँ नहीं मिलेंगी। मैं आपको बताता हूं, इससे भी महत्वपूर्ण बात, जो मैंने सीखा है।

सबसे पहले, वहाँ कोई रास्ता नहीं है कि आप का एक पूर्ण कार्यान्वयन प्राप्त करने के लिए जा रहा IList<T>है लॉकलेस और थ्रेड-सुरक्षित है। विशेष रूप से, यादृच्छिक सम्मिलन और निष्कासन काम नहीं कर रहे हैं , जब तक कि आप O (1) यादृच्छिक अभिगम (यानी, जब तक कि आप "धोखा" न करें और केवल कुछ प्रकार की लिंक की गई सूची का उपयोग करें और अनुक्रमण को चूसने दें)।

क्या मैं सोचा था कि सार्थक हो सकता है एक धागा सुरक्षित, की सीमित सबसेट था IList<T>: विशेष रूप से, एक है कि एक की अनुमति होगी Addऔर यादृच्छिक प्रदान केवल पढ़ने के सूचकांक द्वारा एक्सेस (लेकिन कोई Insert, RemoveAtआदि, और भी कोई यादृच्छिक लिखने पहुँच)।

यह मेरे ConcurrentList<T>कार्यान्वयन का लक्ष्य था । लेकिन जब मैंने मल्टीथ्रेडेड परिदृश्यों में इसके प्रदर्शन का परीक्षण किया, तो मैंने पाया कि बस सिंक्रनाइज़ करना एक List<T>तेजी से जोड़ता है । मूल रूप से, जोड़ने के लिए List<T>पहले से ही तेज़ बिजली है; सम्मिलित कम्प्यूटेशनल चरणों की जटिलता miniscule है (एक सूचकांक में वृद्धि और एक सरणी में एक तत्व को असाइन करें । यह वास्तव में है )। आपको इस पर किसी भी प्रकार के लॉक विवाद को देखने के लिए एक टन समवर्ती लिखने की आवश्यकता होगी ; और फिर भी, प्रत्येक लेखन का औसत प्रदर्शन अभी भी अधिक महंगा यद्यपि लॉकलेस कार्यान्वयन को हरा देगा ConcurrentList<T>

अपेक्षाकृत दुर्लभ घटना में कि सूची के आंतरिक सरणी को खुद को आकार देने की आवश्यकता होती है, आप एक छोटी सी लागत का भुगतान करते हैं। इसलिए अंत में मैंने निष्कर्ष निकाला कि यह एक आला परिदृश्य था जहां एक ऐड-ओनली ConcurrentList<T>कलेक्शन टाइप का मतलब होता है: जब आप हर एक कॉल पर एक तत्व जोड़ने के कम ओवरहेड की गारंटी चाहते हैं (इसलिए, एक परिशोधित प्रदर्शन लक्ष्य के विपरीत)।

यह लगभग उतना उपयोगी नहीं है जितना कि आप सोचेंगे।


52
और अगर आपको कुछ इसी तरह की जरूरत है, तो List<T>पुराने-स्कुल, मॉनिटर-आधारित सिंक्रोनाइज़ेशन का उपयोग करता है, SynchronizedCollection<T>बीसीएल में छिपा हुआ है: msdn.microsoft.com/en-us/library/ms668265.aspx
ल्यूक

8
एक छोटा जोड़: आकार बदलने वाले परिदृश्य से बचने के लिए क्षमता निर्माण पैरामीटर का उपयोग करें।
हेनक होल्टरमैन

2
सबसे बड़ी परिस्तिथि जहां ConcurrentListजीत होगी वह तब होगा जब सूची में जोड़ने के लिए पूरी गतिविधि नहीं होगी, लेकिन कई समवर्ती पाठक हैं। एक रीडर्स ओवरहेड को एकल मेमोरी-बैरियर को कम कर सकता है (और यह भी खत्म कर सकता है कि अगर पाठकों को थोड़ा-सा बासी डेटा की चिंता नहीं थी)।
सुपरकैट

2
@ केविन: यह ConcurrentList<T>इस तरह के एक फैशन का निर्माण करने के लिए बहुत तुच्छ है कि पाठकों को अपेक्षाकृत कम जोड़े गए ओवरहेड के साथ किसी भी लॉकिंग की आवश्यकता के बिना सुसंगत स्थिति देखने की गारंटी है। जब सूची उदाहरण आकार 32 से 64 तक फैलती है, तो आकार -32 सरणी रखें और एक नया आकार -64 सरणी बनाएं। अगले 32 आइटमों में से प्रत्येक को जोड़ते समय, इसे नए सरणी के 32-63 के स्लॉट में डालें और एक पुरानी वस्तु को आकार -32 के सरणी से नए में कॉपी करें। 64 वें आइटम को शामिल किए जाने तक, पाठक 0-31 आइटम के लिए आकार -32 सरणी में और आइटम 32-63 के लिए आकार -64 सरणी में देखेंगे।
सुपरकैट

2
64 वें आइटम को जोड़ने के बाद, आकार -32 सरणी अभी भी 0-31 आइटम लाने के लिए काम करेगा, लेकिन पाठकों को अब इसका उपयोग करने की आवश्यकता नहीं होगी। वे सभी आइटम 0-63 के लिए आकार -64 सरणी का उपयोग कर सकते हैं, और 64-127 आइटम के लिए आकार -128 सरणी। चुनने के ओवरहेड का उपयोग करने के लिए दो सरणियों में से एक, साथ ही यदि वांछित है तो एक मेमोरी बैरियर, सबसे कुशल पाठक-लेखक लॉक कल्पना के ओवरहेड से भी कम होगा। राइट्स को संभवतः ताले का उपयोग करना चाहिए (लॉक-फ्री संभव होगा, खासकर अगर किसी ने हर प्रविष्टि के साथ एक नई वस्तु बनाने का मन नहीं बनाया, लेकिन ताला सस्ता होना चाहिए।
सुपरकैट

38

क्या आप एक समवर्ती के लिए उपयोग करेंगे?

एक थ्रेडेड दुनिया में रैंडम एक्सेस कंटेनर की अवधारणा उतनी उपयोगी नहीं है जितनी यह दिखाई दे सकती है। बयान

  if (i < MyConcurrentList.Count)  
      x = MyConcurrentList[i]; 

एक पूरे के रूप में अभी भी धागा सुरक्षित नहीं होगा।

एक समवर्ती सूची बनाने के बजाय, वहाँ क्या है के साथ समाधान बनाने की कोशिश करें। सबसे आम कक्षाएं हैं समवर्तीबाग और विशेष रूप से ब्लॉकिंगकोलेक्शन।


अच्छी बात। फिर भी जो मैं कर रहा हूं, वह थोड़ा और सांसारिक है। मैं सिर्फ समवर्तीबाग <टी> को एक इलिस्ट <टी> में निर्दिष्ट करने का प्रयास कर रहा हूं। मैं अपनी संपत्ति को IEnumerable <T> पर स्विच कर सकता हूं, लेकिन फिर मैं इसे भर नहीं सकता।
एलन

1
@ एलन: सूची को लॉक किए बिना इसे लागू करने का कोई तरीका नहीं है। चूंकि आप पहले से ही Monitorऐसा करने के लिए उपयोग कर सकते हैं , इसलिए समवर्ती सूची का कोई कारण नहीं है।
बिली ओनली

6
@dcp - हाँ यह स्वाभाविक रूप से गैर-थ्रेड-सुरक्षित है। समवर्ती छायांकन में विशेष विधियां हैं जो एक परमाणु संचालन में ऐसा करती हैं, जैसे AddOrUpdate, GetOrAdd, TryUpdate, आदि। उनके पास अभी भी ContainsKey है क्योंकि कभी-कभी आप केवल यह जानना चाहते हैं कि क्या कुंजी शब्दकोश को संशोधित करने के लिए है (लगता है कि HashSet)
Zarat

3
@dcp - ContainsKey अपने आप से थ्रेडसेफ़ है, आपका उदाहरण (ContainsKey!) सिर्फ एक दौड़ की स्थिति है क्योंकि आप पहले निर्णय के आधार पर एक दूसरी कॉल करते हैं, जो उस बिंदु पर पहले से ही पुराना हो सकता है।
जरात

2
हेंक, मैं सहमत नहीं हूँ। मुझे लगता है कि एक सरल परिदृश्य है जहां यह बहुत उपयोगी हो सकता है। वर्कर थ्रेड इसमें लिखेगा यूआई थ्रेड पढ़ेगा और तदनुसार इंटरफ़ेस अपडेट करेगा। यदि आप क्रमबद्ध तरीके से आइटम जोड़ना चाहते हैं, तो उसे यादृच्छिक अभिगम लिखने की आवश्यकता होगी। आप डेटा के लिए एक स्टैक और एक दृश्य का भी उपयोग कर सकते हैं, लेकिन आपको 2 संग्रह बनाए रखने होंगे :-(।
एरिक ओउलेट

19

पहले से ही प्रदान किए गए महान उत्तरों के लिए सभी सम्मान के साथ, कई बार मैं बस एक थ्रेड-सुरक्षित IList चाहता हूं। उन्नत या फैंसी कुछ भी नहीं। प्रदर्शन कई मामलों में महत्वपूर्ण है, लेकिन कई बार यह सिर्फ एक चिंता का विषय नहीं है। हां, "ट्राइगेटवैल्यू" आदि जैसे तरीकों के बिना हमेशा चुनौतियां बनी रहती हैं, लेकिन ज्यादातर मामलों में मैं बस कुछ ऐसा चाहता हूं, जो मुझे हर चीज के बारे में चिंता करने की आवश्यकता के बिना गणना कर सकता है। और हाँ, किसी को शायद मेरे कार्यान्वयन में कुछ "बग" मिल सकता है जो गतिरोध या कुछ और हो सकता है (मुझे लगता है) लेकिन ईमानदार होने देता है: जब बहु-थ्रेडिंग की बात आती है, यदि आप अपना कोड सही तरीके से नहीं लिखते हैं, तो वैसे भी गतिरोध चल रहा है। इस बात को ध्यान में रखते हुए मैंने एक साधारण समवर्ती साहित्य कार्यान्वयन करने का निर्णय लिया जो इन बुनियादी आवश्यकताओं को प्रदान करता है।

और इसके लायक के लिए: मैंने नियमित सूची और समवर्ती सूची में 10,000,000 वस्तुओं को जोड़ने का एक मूल परीक्षण किया और परिणाम थे:

सूची समाप्त: 7793 मिलीसेकंड। समाप्‍त समाप्‍त: 8064 मिलीसेकंड।

public class ConcurrentList<T> : IList<T>, IDisposable
{
    #region Fields
    private readonly List<T> _list;
    private readonly ReaderWriterLockSlim _lock;
    #endregion

    #region Constructors
    public ConcurrentList()
    {
        this._lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
        this._list = new List<T>();
    }

    public ConcurrentList(int capacity)
    {
        this._lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
        this._list = new List<T>(capacity);
    }

    public ConcurrentList(IEnumerable<T> items)
    {
        this._lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
        this._list = new List<T>(items);
    }
    #endregion

    #region Methods
    public void Add(T item)
    {
        try
        {
            this._lock.EnterWriteLock();
            this._list.Add(item);
        }
        finally
        {
            this._lock.ExitWriteLock();
        }
    }

    public void Insert(int index, T item)
    {
        try
        {
            this._lock.EnterWriteLock();
            this._list.Insert(index, item);
        }
        finally
        {
            this._lock.ExitWriteLock();
        }
    }

    public bool Remove(T item)
    {
        try
        {
            this._lock.EnterWriteLock();
            return this._list.Remove(item);
        }
        finally
        {
            this._lock.ExitWriteLock();
        }
    }

    public void RemoveAt(int index)
    {
        try
        {
            this._lock.EnterWriteLock();
            this._list.RemoveAt(index);
        }
        finally
        {
            this._lock.ExitWriteLock();
        }
    }

    public int IndexOf(T item)
    {
        try
        {
            this._lock.EnterReadLock();
            return this._list.IndexOf(item);
        }
        finally
        {
            this._lock.ExitReadLock();
        }
    }

    public void Clear()
    {
        try
        {
            this._lock.EnterWriteLock();
            this._list.Clear();
        }
        finally
        {
            this._lock.ExitWriteLock();
        }
    }

    public bool Contains(T item)
    {
        try
        {
            this._lock.EnterReadLock();
            return this._list.Contains(item);
        }
        finally
        {
            this._lock.ExitReadLock();
        }
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        try
        {
            this._lock.EnterReadLock();
            this._list.CopyTo(array, arrayIndex);
        }
        finally
        {
            this._lock.ExitReadLock();
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        return new ConcurrentEnumerator<T>(this._list, this._lock);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return new ConcurrentEnumerator<T>(this._list, this._lock);
    }

    ~ConcurrentList()
    {
        this.Dispose(false);
    }

    public void Dispose()
    {
        this.Dispose(true);
    }

    private void Dispose(bool disposing)
    {
        if (disposing)
            GC.SuppressFinalize(this);

        this._lock.Dispose();
    }
    #endregion

    #region Properties
    public T this[int index]
    {
        get
        {
            try
            {
                this._lock.EnterReadLock();
                return this._list[index];
            }
            finally
            {
                this._lock.ExitReadLock();
            }
        }
        set
        {
            try
            {
                this._lock.EnterWriteLock();
                this._list[index] = value;
            }
            finally
            {
                this._lock.ExitWriteLock();
            }
        }
    }

    public int Count
    {
        get
        {
            try
            {
                this._lock.EnterReadLock();
                return this._list.Count;
            }
            finally
            {
                this._lock.ExitReadLock();
            }
        }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }
    #endregion
}

    public class ConcurrentEnumerator<T> : IEnumerator<T>
{
    #region Fields
    private readonly IEnumerator<T> _inner;
    private readonly ReaderWriterLockSlim _lock;
    #endregion

    #region Constructor
    public ConcurrentEnumerator(IEnumerable<T> inner, ReaderWriterLockSlim @lock)
    {
        this._lock = @lock;
        this._lock.EnterReadLock();
        this._inner = inner.GetEnumerator();
    }
    #endregion

    #region Methods
    public bool MoveNext()
    {
        return _inner.MoveNext();
    }

    public void Reset()
    {
        _inner.Reset();
    }

    public void Dispose()
    {
        this._lock.ExitReadLock();
    }
    #endregion

    #region Properties
    public T Current
    {
        get { return _inner.Current; }
    }

    object IEnumerator.Current
    {
        get { return _inner.Current; }
    }
    #endregion
}

5
ठीक है, पुराना उत्तर है, लेकिन फिर भी: RemoveAt(int index)कभी भी थ्रेड-सेफ नहीं है, Insert(int index, T item)केवल इंडेक्स == 0 के लिए सुरक्षित है, की वापसी IndexOf()तुरंत पुरानी हो गई है आदि के बारे में भी शुरू न करें this[int]
हेनक होल्टरमैन

2
और आपको जरूरत नहीं है और ~ फाइनल () नहीं चाहिए।
हेनक होल्टरमैन

2
आप कहते हैं कि आपने गतिरोध की संभावना को रोकने के लिए छोड़ दिया है - और समवर्ती का उपयोग करके आसानीReaderWriterLockSlim से गतिरोध के लिए एक एकल बनाया जा सकता है । हालाँकि आप इसका उपयोग नहीं करते हैं, आप लॉक को बाहर तक पहुँच योग्य नहीं बनाते हैं, और आप उदाहरण के लिए एक विधि को कॉल नहीं करते हैं जो रीड लॉक रखते हुए राइट लॉक में प्रवेश करता है, इसलिए आपकी कक्षा का उपयोग करके कोई गतिरोध नहीं करता है संभावना है। EnterUpgradeableReadLock()
यूजीन बेरेसोव्स्की

1
गैर-समवर्ती इंटरफ़ेस समवर्ती पहुँच के लिए उपयुक्त नहीं है। उदा। निम्न परमाणु नहीं है var l = new ConcurrentList<string>(); /* ... */ l[0] += "asdf";। सामान्य तौर पर, कोई भी पढ़ा-लिखा कॉम्बो समवर्ती रूप से किए जाने पर आपको गहरी मुसीबत में डाल सकता है। यही कारण है कि समवर्ती डेटा संरचनाओं आम तौर पर उन लोगों के लिए तरह-तरह प्रदान ConcurrentDictionaryकी AddOrGetआदि नायब आपका निरंतर (और अनावश्यक क्योंकि सदस्य पहले ही अंडरस्कोर द्वारा इस तरह के रूप में चिह्नित कर रहे हैं) की पुनरावृत्ति this.clutters।
यूजीन बेरेसोव्स्की

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

11

ConcurrentList(एक रिज़र्व करने योग्य सरणी के रूप में, एक लिंक की गई सूची नहीं) नॉनब्लॉकिंग ऑपरेशन के साथ लिखना आसान नहीं है। इसका एपीआई "समवर्ती" संस्करण में अच्छी तरह से अनुवाद नहीं करता है।


12
यह न केवल लिखना मुश्किल है, बल्कि एक उपयोगी इंटरफ़ेस का पता लगाना भी मुश्किल है।
कोडइन्चोस 19

11

कॉनकंट्रोलिस्ट नहीं होने का कारण यह है कि यह मौलिक रूप से नहीं लिखा जा सकता है। यही कारण है कि IList में कई महत्वपूर्ण संचालन सूचकांकों पर निर्भर करते हैं, और यह कि सिर्फ सादा काम नहीं करेगा। उदाहरण के लिए:

int catIndex = list.IndexOf("cat");
list.Insert(catIndex, "dog");

"बिल्ली" से पहले "कुत्ते" को सम्मिलित करने के बाद लेखक पर जो प्रभाव पड़ रहा है, लेकिन एक बहुस्तरीय वातावरण में, कोड की उन दो पंक्तियों के बीच सूची में कुछ भी हो सकता है। उदाहरण के लिए, एक अन्य धागा list.RemoveAt(0), पूरी सूची को बाईं ओर स्थानांतरित कर सकता है , लेकिन महत्वपूर्ण रूप से, catIndex नहीं बदलेगा। यहां प्रभाव यह है कि Insertऑपरेशन वास्तव में "कुत्ते" को बिल्ली के बाद रखा जाएगा , इससे पहले नहीं।

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

अगर आपको लगता है कि आपको समवर्ती सूची की आवश्यकता है, तो वास्तव में सिर्फ दो संभावनाएं हैं:

  1. क्या आप वास्तव में जरूरत है एक समवर्ती है
  2. आपको अपना खुद का संग्रह बनाने की आवश्यकता है, शायद एक सूची और अपने स्वयं के संगरोध नियंत्रण के साथ लागू किया गया।

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


5

ऐसे मामलों में जहां बहुत अधिक पढ़े जाते हैं लिखते हैं, या (हालांकि अक्सर) लिखते हैं गैर-समवर्ती , एक कॉपी-ऑन-राइट दृष्टिकोण उपयुक्त हो सकता है।

नीचे दिखाया गया कार्यान्वयन है

  • लॉकसेल
  • समवर्ती पाठ के लिए धधकते हुए तेज , भले ही समवर्ती संशोधन चल रहे हों - चाहे वे कितना भी समय लें
  • क्योंकि "स्नैपशॉट" अपरिवर्तनीय हैं, लॉकलेस एटमॉसिटी संभव है, अर्थातvar snap = _list; snap[snap.Count - 1]; कभी नहीं (अच्छी तरह से, पाठ्यक्रम की एक खाली सूची को छोड़कर) फेंक देंगे, और आपको थ्रेड-सेफ एन्यूमरेशन भी मुफ्त में स्नैपशॉट शब्दार्थ के साथ मिलता है .. कैसे LOVE अपरिवर्तनीयता!
  • किसी भी डेटा संरचना के लिए लागू उदारतापूर्वक , और किसी भी प्रकार के संशोधन पर
  • मृत सरल , यानी कोड को पढ़कर परीक्षण करना, डीबग करना आसान है
  • प्रयोग करने योग्य .Net 3.5 में

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

  1. संरचना को क्लोन करें
  2. क्लोन पर संशोधन करें
  3. संशोधित क्लोन के संदर्भ में परमाणु स्वैप

कोड

static class CopyOnWriteSwapper
{
    public static void Swap<T>(ref T obj, Func<T, T> cloner, Action<T> op)
        where T : class
    {
        while (true)
        {
            var objBefore = Volatile.Read(ref obj);
            var newObj = cloner(objBefore);
            op(newObj);
            if (Interlocked.CompareExchange(ref obj, newObj, objBefore) == objBefore)
                return;
        }
    }
}

प्रयोग

CopyOnWriteSwapper.Swap(ref _myList,
    orig => new List<string>(orig),
    clone => clone.Add("asdf"));

यदि आपको अधिक प्रदर्शन की आवश्यकता है, तो यह विधि को ungenerify करने में मदद करेगा, जैसे कि हर प्रकार के संशोधन के लिए एक विधि बनाएं (Add, Remove, ...) जो आप चाहते हैं, और फ़ंक्शन कोड को हार्ड कोड clonerऔर op

एनबी # 1 यह आपकी ज़िम्मेदारी है कि कोई भी (कथित रूप से) अपरिवर्तनीय डेटा संरचना को संशोधित न करे। इसे रोकने के लिए जेनेरिक कार्यान्वयन में हम कुछ नहीं कर सकते हैं , लेकिन जब विशेषज्ञता होती है List<T>, तो आप List.AsReadOnly () का उपयोग करके संशोधन के खिलाफ रख सकते हैं

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

NB # 3 यदि आपकी डेटा संरचना बहुत बड़ी है और आप इसे बार-बार संशोधित करते हैं, तो कॉपी-ऑल-ऑन-राइट अप्रोच मेमोरी खपत और सीपीयू की लागत दोनों के लिए निषेधात्मक हो सकती है। उस स्थिति में, आप इसके बजाय MS के अपरिवर्तनीय संग्रह का उपयोग करना चाह सकते हैं ।


3

System.Collections.Generic.List<t>कई पाठकों के लिए पहले से ही सुरक्षित है। कई लेखकों के लिए इसे थ्रेड को सुरक्षित बनाने की कोशिश करने का कोई मतलब नहीं होगा। (कारणों के लिए हेंक और स्टीफन ने पहले ही उल्लेख किया है)


आप एक परिदृश्य नहीं देख सकते हैं जहाँ मैं एक सूची में 5 धागे जोड़ सकता हूं? इस तरह आप देख सकते हैं कि सूची पूरी होने से पहले ही रिकॉर्ड जमा कर चुकी है।
एलन

9
@Alan - जो एक समवर्ती क्यू, समवर्ती स्टैक या समवर्ती बैगा होगा। एक समवर्तीList की समझ बनाने के लिए आपको एक उपयोग-मामला प्रदान करना चाहिए जहाँ उपलब्ध कक्षाएं पर्याप्त नहीं हैं। मैं यह नहीं देखता कि जब सूचकांक में तत्व समवर्ती हटाने के माध्यम से यादृच्छिक रूप से बदल सकते हैं तो मैं अनुक्रमित पहुंच क्यों चाहूंगा। और "लॉक" पढ़ने के लिए आप पहले से ही मौजूदा समवर्ती वर्गों के स्नैपशॉट ले सकते हैं और उन्हें एक सूची में डाल सकते हैं।
ज़ारत

आप सही हैं - मैं अनुक्रमित पहुँच नहीं चाहता। मैं आम तौर पर IList <T> का उपयोग एक IEnumerable के लिए एक प्रॉक्सी के रूप में करता हूं जिससे मैं कर सकता हूं। (T) नए तत्व। यहीं से प्रश्न आता है, वास्तव में।
एलन

@ एलन: फिर आपको एक कतार चाहिए, सूची नहीं।
बिली ओनली

3
मैं सोचता हूं कि आप गलत हैं। कहना: कई पाठकों के लिए सुरक्षित होने का मतलब यह नहीं है कि आप एक ही समय में नहीं लिख सकते। लिखने का मतलब डिलीट भी होगा और इसमें पुनरावृति करते समय यदि आप डिलीट करेंगे तो आपको एक त्रुटि मिलेगी।
एरिक ओयुलेट

2

कुछ लोगों ने कुछ माल बिंदुओं (और मेरे कुछ विचारों) को हाइल किया:

  • यह यादृच्छिक अभिगमक (अनुक्रमणिका) के लिए पागल लग सकता है लेकिन मेरे लिए यह ठीक प्रतीत होता है। आपको केवल यह सोचना है कि बहु-थ्रेडेड संग्रह पर कई विधियाँ हैं जो अनुक्रमणिका और हटाएं जैसे विफल हो सकते हैं। आप "असफल" या केवल "अंत में जोड़ें" जैसे लिखने के लिए विफलता (एक्शन) को परिभाषित कर सकते हैं।
  • यह इसलिए नहीं है कि यह एक बहुस्तरीय संग्रह है कि इसका उपयोग हमेशा एक बहुआयामी संदर्भ में किया जाएगा। या यह केवल एक लेखक और एक पाठक द्वारा भी इस्तेमाल किया जा सकता है।
  • एक सुरक्षित तरीके से अनुक्रमणिका का उपयोग करने में सक्षम होने का एक अन्य तरीका इसकी जड़ (यदि सार्वजनिक किया गया है) का उपयोग करके संग्रह के लॉक में क्रियाओं को लपेटना हो सकता है।
  • कई लोगों के लिए, एक रूटलॉक को दिखाई देने से एगिस्ट "गुड प्रैक्टिस" हो जाता है। मैं इस बिंदु के बारे में 100% निश्चित नहीं हूं क्योंकि अगर यह छिपा हुआ है तो आप उपयोगकर्ता को बहुत अधिक लचीलेपन को दूर करते हैं। हमें हमेशा याद रखना है कि प्रोग्रामिंग मल्टीथ्रेड किसी के लिए नहीं है। हम हर तरह के गलत इस्तेमाल को नहीं रोक सकते।
  • Microsoft को कुछ काम करना होगा और Multithreaded संग्रह के उचित उपयोग को शुरू करने के लिए कुछ नए मानक को परिभाषित करना होगा। पहले IEnumerator में एक MoveNext नहीं होना चाहिए, लेकिन एक GetNext होना चाहिए जो सही या गलत लौटाए और टाइप T का एक आउट पैरामेटर प्राप्त करें (इस तरह यह पुनरावृत्ति अब अवरुद्ध नहीं होगी)। इसके अलावा, Microsoft पहले से ही "आंतरिक रूप से foreach" का उपयोग कर रहा है, लेकिन कभी-कभी "उपयोग" (संग्रह दृश्य में बग और संभवतः अधिक स्थानों पर) के साथ इसे लपेटे बिना सीधे IEnumerator का उपयोग करें - IEnumerator का रैपिंग उपयोग Microsoft द्वारा अनुशंसित अनुशंसा है। यह बग सुरक्षित पुनरावृत्ति के लिए अच्छी क्षमता को हटाता है ... Iterator जो कि कंस्ट्रक्टर में संग्रह को लॉक करता है और इसकी डिस्पोज़ विधि पर अनलॉक होता है - एक अवरुद्ध ब्लीच विधि के लिए।

वह उत्तर नहीं है। यह केवल टिप्पणियां हैं जो वास्तव में एक विशिष्ट स्थान पर फिट नहीं होती हैं।

... मेरा निष्कर्ष है, Microsoft को "Foreach" में कुछ गहरे बदलाव करने हैं ताकि मल्टीथ्रेड संग्रह का उपयोग करना आसान हो सके। इसके अलावा यह IEnumerator उपयोग के स्वयं के नियमों का पालन करना है। इससे पहले, हम एक मल्टीट्रेडलिस्ट को आसानी से लिख सकते हैं जो एक अवरोधक का उपयोग करेगा लेकिन यह "IList" का पालन नहीं करेगा। इसके बजाय, आपको स्वयं "IListPersonnal" इंटरफ़ेस को परिभाषित करना होगा जो अपवाद के बिना "सम्मिलित करें", "निकालें" और यादृच्छिक अभिगमक (अनुक्रमणिका) पर विफल हो सकता है। लेकिन अगर यह मानक नहीं है तो इसका उपयोग कौन करना चाहेगा?


कोई भी आसानी से लिख सकता है ConcurrentOrderedBag<T>जिसमें केवल-पढ़ने का कार्यान्वयन शामिल होगा IList<T>, लेकिन यह पूरी तरह से थ्रेड-सुरक्षित int Add(T value)पद्धति भी प्रदान करेगा । मैं नहीं देखता कि किसी भी ForEachबदलाव की आवश्यकता क्यों होगी। हालाँकि Microsoft स्पष्ट रूप से ऐसा नहीं कहता है, लेकिन उनके अभ्यास से यह पता चलता है कि IEnumerator<T>जब यह बनाया गया था तब मौजूद सामग्री को फिर से जमा करने के लिए यह पूरी तरह स्वीकार्य है ; यदि संग्रहकर्ता ग्लिच-मुक्त ऑपरेशन की गारंटी देने में असमर्थ होगा, तो संग्रह-संशोधित अपवाद की आवश्यकता है।
सुपरकैट

एमटी संग्रह के माध्यम से Iterating, जिस तरह से यह डिजाइन है वह नेतृत्व कर सकता है, जैसा कि आपने कहा, एक अपवाद के लिए ... जो मुझे नहीं पता है। क्या आप सभी अपवादों में फंसेंगे? मेरी अपनी पुस्तक में अपवाद अपवाद है और कोड के सामान्य निष्पादन में नहीं होना चाहिए। अन्यथा, अपवाद को रोकने के लिए, आपको संग्रह को लॉक करना होगा या एक कॉपी (सुरक्षित तरीके से-यानी लॉक) में प्राप्त करना होगा या संग्रह में बहुत जटिल तंत्र को लागू करना होगा ताकि समरूपता के कारण होने वाले अपवाद को रोका जा सके। हालांकि मेरा यह था कि IEnumeratorMT को जोड़ना अच्छा होगा जो संग्रह को लॉक करेगा जबकि प्रत्येक के लिए होगा और संबंधित कोड जोड़ देगा ...
Eric Ouellet

दूसरी बात जो हो सकती है वह यह है कि जब आपको एक सूचना-पत्र प्राप्त होता है, तो आप संग्रह को लॉक कर सकते हैं और जब आपका सूचना-पत्र GC एकत्र होता है, तो आप संग्रह को अनलॉक कर सकते हैं। Microsfot के अनुसार वे पहले से ही जांचते हैं कि क्या IEnumerable भी एक आईडीआई-प्रयोज्य है और यदि किसी ForEach के अंत में GC को कॉल करें। मुख्य समस्या यह है कि वे जीसी को कॉल किए बिना IEnumerable का उपयोग कहीं और करते हैं, आप तब उस पर भरोसा नहीं कर सकते। IEnumerable सक्षम ताला के लिए एक नया स्पष्ट एमटी इंटरफ़ेस होने से समस्या का समाधान होगा, कम से कम इसका एक हिस्सा। (यह लोगों को इसे न कहने से नहीं रोकेगा)।
एरिक ओउलेट

किसी सार्वजनिक GetEnumeratorपद्धति के लिए एक संग्रह को छोड़ने के बाद उसे बंद करना बहुत बुरा रूप है; इस तरह के डिजाइन आसानी से गतिरोध पैदा कर सकते हैं। इस बात का IEnumerable<T>कोई संकेत नहीं देता है कि क्या संग्रह के संशोधित होने पर भी गणना की उम्मीद की जा सकती है; जो सबसे अच्छा काम कर सकता है, वह है अपने तरीकों को लिखना ताकि वे ऐसा करेंगे, और ऐसे तरीके हैं जो IEnumerable<T>इस तथ्य को दस्तावेज़ में स्वीकार करते हैं कि यदि IEnumerable<T>थ्रेड-सुरक्षित गणना का समर्थन करता है तो केवल थ्रेड-सुरक्षित होगा ।
सुपरकैट

सबसे अधिक उपयोगी क्या होता अगर IEnumerable<T>रिटर्न प्रकार के साथ एक "स्नैपशॉट" विधि शामिल होती IEnumerable<T>। अपरिवर्तनीय संग्रह खुद को वापस कर सकते हैं; एक बंधे हुए संग्रह अगर कुछ और खुद को कॉपी नहीं कर सकता है List<T>या उस पर T[]कॉल GetEnumeratorकर सकता है। कुछ अनबाउंड संग्रह लागू हो सकते हैं Snapshot, और जो अपनी सामग्री के साथ एक सूची को भरने की कोशिश किए बिना एक अपवाद को फेंकने में सक्षम नहीं होंगे।
सुपरकैट

1

क्रमिक रूप से निष्पादित कोड में प्रयुक्त डेटा संरचनाएं (अच्छी तरह से लिखित) समवर्ती निष्पादन कोड से भिन्न होती हैं। कारण यह है कि अनुक्रमिक कोड का तात्पर्य अंतर्निहित आदेश से है। समवर्ती कोड हालांकि किसी भी आदेश का मतलब नहीं है; बेहतर अभी तक यह किसी भी परिभाषित आदेश की कमी का मतलब है!

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

याद रखें कि संगणना एक डेटा समस्या है, कोड समस्या नहीं! आप पहले कोड (या मौजूदा अनुक्रमिक कोड को फिर से लिखना) को लागू नहीं कर सकते हैं और एक अच्छी तरह से डिज़ाइन किया गया समवर्ती समाधान प्राप्त कर सकते हैं। आपको पहले यह ध्यान में रखते हुए डेटा संरचनाओं को डिज़ाइन करने की आवश्यकता है कि समवर्ती व्यवस्था में निहित आदेश मौजूद नहीं है।


1

यदि आप बहुत सारी वस्तुओं के साथ काम नहीं कर रहे हैं तो लॉकलेस कॉपी और अप्रोच बढ़िया काम करता है। यहाँ एक वर्ग मैंने लिखा है:

public class CopyAndWriteList<T>
{
    public static List<T> Clear(List<T> list)
    {
        var a = new List<T>(list);
        a.Clear();
        return a;
    }

    public static List<T> Add(List<T> list, T item)
    {
        var a = new List<T>(list);
        a.Add(item);
        return a;
    }

    public static List<T> RemoveAt(List<T> list, int index)
    {
        var a = new List<T>(list);
        a.RemoveAt(index);
        return a;
    }

    public static List<T> Remove(List<T> list, T item)
    {
        var a = new List<T>(list);
        a.Remove(item);
        return a;
    }

}

उदाहरण का उपयोग: ऑर्डर_BUY = CopyAndWriteList.Clear (ऑर्डर_BUY);


लॉक करने के बजाय, यह सूची की एक प्रति बनाता है, सूची को संशोधित करता है और नई सूची का संदर्भ सेट करता है। तो किसी भी अन्य धागे जो पुनरावृत्ति कर रहे हैं, उन्हें कोई समस्या नहीं होगी।
रोब द क्वांट

0

मैंने ब्रायन के समान एक को लागू किया । मेरा अलग है:

  • मैं सीधे सरणी का प्रबंधन करता हूं।
  • मैं कोशिश ब्लॉक के भीतर ताले दर्ज नहीं है।
  • मैं उपयोग करता हूं yield return एक एन्यूमरेटर के उत्पादन के लिए ।
  • मैं ताला पुनरावृत्ति का समर्थन करता हूं। यह पुनरावृत्ति के दौरान सूची से रीड की अनुमति देता है।
  • मैं जहां संभव हो, वहां अपग्रेडेबल रीड लॉक्स का उपयोग करता हूं।
  • DoSyncऔर GetSyncअनुक्रमिक बातचीत की अनुमति देने वाले तरीके जिन्हें सूची में अनन्य पहुंच की आवश्यकता होती है।

कोड :

public class ConcurrentList<T> : IList<T>, IDisposable
{
    private ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
    private int _count = 0;

    public int Count
    {
        get
        { 
            _lock.EnterReadLock();
            try
            {           
                return _count;
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }
    }

    public int InternalArrayLength
    { 
        get
        { 
            _lock.EnterReadLock();
            try
            {           
                return _arr.Length;
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }
    }

    private T[] _arr;

    public ConcurrentList(int initialCapacity)
    {
        _arr = new T[initialCapacity];
    }

    public ConcurrentList():this(4)
    { }

    public ConcurrentList(IEnumerable<T> items)
    {
        _arr = items.ToArray();
        _count = _arr.Length;
    }

    public void Add(T item)
    {
        _lock.EnterWriteLock();
        try
        {       
            var newCount = _count + 1;          
            EnsureCapacity(newCount);           
            _arr[_count] = item;
            _count = newCount;                  
        }
        finally
        {
            _lock.ExitWriteLock();
        }       
    }

    public void AddRange(IEnumerable<T> items)
    {
        if (items == null)
            throw new ArgumentNullException("items");

        _lock.EnterWriteLock();

        try
        {           
            var arr = items as T[] ?? items.ToArray();          
            var newCount = _count + arr.Length;
            EnsureCapacity(newCount);           
            Array.Copy(arr, 0, _arr, _count, arr.Length);       
            _count = newCount;
        }
        finally
        {
            _lock.ExitWriteLock();          
        }
    }

    private void EnsureCapacity(int capacity)
    {   
        if (_arr.Length >= capacity)
            return;

        int doubled;
        checked
        {
            try
            {           
                doubled = _arr.Length * 2;
            }
            catch (OverflowException)
            {
                doubled = int.MaxValue;
            }
        }

        var newLength = Math.Max(doubled, capacity);            
        Array.Resize(ref _arr, newLength);
    }

    public bool Remove(T item)
    {
        _lock.EnterUpgradeableReadLock();

        try
        {           
            var i = IndexOfInternal(item);

            if (i == -1)
                return false;

            _lock.EnterWriteLock();
            try
            {   
                RemoveAtInternal(i);
                return true;
            }
            finally
            {               
                _lock.ExitWriteLock();
            }
        }
        finally
        {           
            _lock.ExitUpgradeableReadLock();
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        _lock.EnterReadLock();

        try
        {    
            for (int i = 0; i < _count; i++)
                // deadlocking potential mitigated by lock recursion enforcement
                yield return _arr[i]; 
        }
        finally
        {           
            _lock.ExitReadLock();
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }

    public int IndexOf(T item)
    {
        _lock.EnterReadLock();
        try
        {   
            return IndexOfInternal(item);
        }
        finally
        {
            _lock.ExitReadLock();
        }
    }

    private int IndexOfInternal(T item)
    {
        return Array.FindIndex(_arr, 0, _count, x => x.Equals(item));
    }

    public void Insert(int index, T item)
    {
        _lock.EnterUpgradeableReadLock();

        try
        {                       
            if (index > _count)
                throw new ArgumentOutOfRangeException("index"); 

            _lock.EnterWriteLock();
            try
            {       
                var newCount = _count + 1;
                EnsureCapacity(newCount);

                // shift everything right by one, starting at index
                Array.Copy(_arr, index, _arr, index + 1, _count - index);

                // insert
                _arr[index] = item;     
                _count = newCount;
            }
            finally
            {           
                _lock.ExitWriteLock();
            }
        }
        finally
        {
            _lock.ExitUpgradeableReadLock();            
        }


    }

    public void RemoveAt(int index)
    {   
        _lock.EnterUpgradeableReadLock();
        try
        {   
            if (index >= _count)
                throw new ArgumentOutOfRangeException("index");

            _lock.EnterWriteLock();
            try
            {           
                RemoveAtInternal(index);
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }
        finally
        {
            _lock.ExitUpgradeableReadLock();            
        }
    }

    private void RemoveAtInternal(int index)
    {           
        Array.Copy(_arr, index + 1, _arr, index, _count - index-1);
        _count--;

        // release last element
        Array.Clear(_arr, _count, 1);
    }

    public void Clear()
    {
        _lock.EnterWriteLock();
        try
        {        
            Array.Clear(_arr, 0, _count);
            _count = 0;
        }
        finally
        {           
            _lock.ExitWriteLock();
        }   
    }

    public bool Contains(T item)
    {
        _lock.EnterReadLock();
        try
        {   
            return IndexOfInternal(item) != -1;
        }
        finally
        {           
            _lock.ExitReadLock();
        }
    }

    public void CopyTo(T[] array, int arrayIndex)
    {       
        _lock.EnterReadLock();
        try
        {           
            if(_count > array.Length - arrayIndex)
                throw new ArgumentException("Destination array was not long enough.");

            Array.Copy(_arr, 0, array, arrayIndex, _count);
        }
        finally
        {
            _lock.ExitReadLock();           
        }
    }

    public bool IsReadOnly
    {   
        get { return false; }
    }

    public T this[int index]
    {
        get
        {
            _lock.EnterReadLock();
            try
            {           
                if (index >= _count)
                    throw new ArgumentOutOfRangeException("index");

                return _arr[index]; 
            }
            finally
            {
                _lock.ExitReadLock();               
            }           
        }
        set
        {
            _lock.EnterUpgradeableReadLock();
            try
            {

                if (index >= _count)
                    throw new ArgumentOutOfRangeException("index");

                _lock.EnterWriteLock();
                try
                {                       
                    _arr[index] = value;
                }
                finally
                {
                    _lock.ExitWriteLock();              
                }
            }
            finally
            {
                _lock.ExitUpgradeableReadLock();
            }

        }
    }

    public void DoSync(Action<ConcurrentList<T>> action)
    {
        GetSync(l =>
        {
            action(l);
            return 0;
        });
    }

    public TResult GetSync<TResult>(Func<ConcurrentList<T>,TResult> func)
    {
        _lock.EnterWriteLock();
        try
        {           
            return func(this);
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public void Dispose()
    {   
        _lock.Dispose();
    }
}

यदि दो धागे एक ही समय tryमें ब्लॉक Removeया इंडेक्सर सेटर की शुरुआत में आते हैं तो क्या होगा ?
जेम्स

@ नाम जो संभव नहीं लगता है। Msdn.microsoft.com/en-us/library/… पर टिप्पणी पढ़ें । इस कोड को चलाने पर, आप उस लॉक को दूसरी बार दर्ज नहीं करेंगे: gist.github.com/ronnieoverby/59b715c3676127a113c3
Ronnie Overby

@ रॉनी ओवरबी: दिलचस्प। यह देखते हुए, मुझे संदेह है कि यदि आप अपग्रेडेबल रीड लॉक को अपग्रेड करने योग्य रीड लॉक और राइट लॉक के बीच के समय में किए गए सभी कार्यों से हटा दें, तो यह बेहतर प्रदर्शन करेगा। किसी भी तरह का लॉक लेने का ओवरहेड इतना अधिक है जाँच से यह देखने के लिए कि क्या पैरामीटर सीमा से बाहर है, जो कि राइट लॉक के अंदर उस चेक को करने से बेहतर प्रदर्शन होगा।
जेम्स

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

1
मैं यह कहते हुए रिकॉर्ड पर जाना चाहता था कि मैं मानता हूं कि IListसमवर्ती परिदृश्यों में शब्दार्थ की उपयोगिता सीमित है। मुझे यह अहसास होने से पहले ही मैंने यह कोड लिखा था। मेरा अनुभव स्वीकार किए गए उत्तर के लेखक के रूप में ही है: मैंने इसे एक कोशिश के साथ दिया था जिसे मैं सिंक्रनाइज़ेशन और IList <T> के बारे में जानता था और मैंने ऐसा करके कुछ सीखा।
रॉनी ओवरबी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.