List.Add () थ्रेड सुरक्षा


91

मैं समझता हूं कि सामान्य रूप से एक सूची थ्रेड सुरक्षित नहीं है, लेकिन क्या किसी सूची में आइटम जोड़ने के साथ कुछ भी गलत है अगर थ्रेड्स सूची पर कोई अन्य संचालन नहीं करते हैं (जैसे कि इसे ट्रैवर्स करना)?

उदाहरण:

List<object> list = new List<object>();
Parallel.ForEach(transactions, tran =>
{
    list.Add(new object());
});


मैंने एक बार समानांतर में चलने वाले कई कार्यों से नई वस्तुओं को जोड़ने के लिए केवल एक सूची <T> का उपयोग किया था। कभी-कभी, बहुत दुर्लभ, जब सभी कार्यों को पूरा करने के बाद सूची के माध्यम से पुनरावृत्ति होती है, तो यह एक रिकॉर्ड के साथ समाप्त हो गया था जो कि अशक्त था, अगर कोई अतिरिक्त सूत्र शामिल नहीं होता, तो ऐसा होने के लिए व्यावहारिक रूप से असंभव होता। मुझे लगता है कि, जब सूची आंतरिक रूप से विस्तार करने के लिए अपने तत्वों को फिर से आवंटित कर रही थी, तो किसी अन्य वस्तु ने किसी अन्य वस्तु को जोड़ने की कोशिश करके इसे गड़बड़ कर दिया। तो यह ऐसा करने के लिए एक अच्छा विचार नहीं है!
— ओसमियमिन

वास्तव में मैं वर्तमान में एक वस्तु के संबंध में @osmiumbin को अनावश्यक रूप से अशक्त होने के कारण देख रहा हूं, जब बस कई धागों से जोड़ा जा रहा है। पुष्टि के लिए धन्यवाद।
— Blackey

जवाबों:


75

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

.NET 4.0 में हमारे पास समवर्ती संग्रह हैं, जो हैंडरली थ्रेडसेफ़ हैं और ताले की आवश्यकता नहीं है।


यह सही समझ में आता है, मैं निश्चित रूप से इसके लिए नए समवर्ती संग्रह को देखूंगा। धन्यवाद।
— e36M3

11
ध्यान दें कि कोई अंतर्निहित ConcurrentListप्रकार नहीं है। समवर्ती बैग, शब्दकोश, ढेर, कतारें आदि हैं, लेकिन कोई सूची नहीं।
— ल्यूक

11

आप वर्तमान दृष्टिकोण धागा-सुरक्षित नहीं है - मैं आपको पूरी तरह से बचने का सुझाव दूंगा - क्योंकि आप मूल रूप से एक डेटा परिवर्तन करते हैं PLINQ एक बेहतर दृष्टिकोण हो सकता है (मुझे पता है कि यह एक सरल उदाहरण है, लेकिन अंत में आप प्रत्येक लेनदेन को दूसरे "राज्य" में पेश कर रहे हैं। "वस्तु)।

List<object> list = transactions.AsParallel()
                                .Select( tran => new object())
                                .ToList();

मैंने सूची के पहलू पर ज़ोर देने के लिए एक बहुत ही सरल उदाहरण प्रस्तुत किया था। बता दें कि मैं इसमें रुचि रखता था। मेरे समानांतर। वास्तव में अच्छी मात्रा में काम करेंगे और एक साधारण डेटा परिवर्तन नहीं होगा। धन्यवाद।
— e36M3

4
समवर्ती संग्रह आपके समानांतर प्रदर्शन को अपंग कर सकते हैं यदि उपयोग नहीं किया जाता है - एक और चीज जो आप कर सकते हैं वह है एक निश्चित आकार के सरणी का उपयोग करना और उस Parallel.Foreachअधिभार का उपयोग करें जो सूचकांक में लेता है - उस स्थिति में प्रत्येक धागा एक अलग सरणी प्रविष्टि में हेरफेर कर रहा है और आपको सुरक्षित होना चाहिए।
— BrokenGlass

6

यह कोई अनुचित बात नहीं है। वहाँ रहे हैं मामलों में यदि वे केवल विधि कहा जाता है जहां तरीकों जो अन्य विधियों के संयोजन में धागे की सुरक्षा के मुद्दों पैदा कर सकता है सुरक्षित हैं।

हालाँकि, यह स्पष्ट रूप से इसका कोई मामला नहीं है, जब आप रिफ्लेक्टर में दिखाए गए कोड पर विचार करते हैं:

public void Add(T item)
{
    if (this._size == this._items.Length)
    {
        this.EnsureCapacity(this._size + 1);
    }
    this._items[this._size++] = item;
    this._version++;
}

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

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


6

यदि आप List.addकई थ्रेड्स से उपयोग करना चाहते हैं और ऑर्डर करने की परवाह नहीं करते हैं, तो आपको शायद किसी Listभी तरह से अनुक्रमण क्षमता की आवश्यकता नहीं है, और इसके बजाय उपलब्ध समवर्ती संग्रह में से कुछ का उपयोग करना चाहिए।

यदि आप इस सलाह को अनदेखा करते हैं और केवल करते हैं add, तो आप addथ्रेड को सुरक्षित बना सकते हैं लेकिन इस तरह अप्रत्याशित क्रम में:

private Object someListLock = new Object(); // only once

...

lock (someListLock)
{
    someList.Add(item);
}

यदि आप इस अप्रत्याशित आदेश को स्वीकार करते हैं, तो संभावना है कि जैसा कि आपने पहले उल्लेख किया है कि एक संग्रह की आवश्यकता नहीं है जो कि अनुक्रमण कर सकता है someList[i]।


5

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

कई संभावित समस्याएँ हैं ... बस नहीं। यदि आपको थ्रेड सुरक्षित संग्रह की आवश्यकता है, तो या तो लॉक का उपयोग करें या System.Collections.Concurrent संग्रह में से एक का उपयोग करें।


5

मैंने इस तरह के ConcurrentBag<T>बजाय अपनी समस्या को हल किया List<T>:

ConcurrentBag<object> list = new ConcurrentBag<object>();
Parallel.ForEach(transactions, tran =>
{
    list.Add(new object());
});

2

क्या किसी सूची में आइटम जोड़ने के साथ कुछ भी गलत है अगर धागे कभी सूची पर कोई अन्य संचालन नहीं करते हैं?

संक्षिप्त उत्तर: हाँ।

दीर्घ उत्तर: नीचे दिए गए कार्यक्रम को चलाएं।

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

class Program
{
    readonly List<int> l = new List<int>();
    const int amount = 1000;
    int toFinish = amount;
    readonly AutoResetEvent are = new AutoResetEvent(false);

    static void Main()
    {
        new Program().Run();
    }

    void Run()
    {
        for (int i = 0; i < amount; i++)
            new Thread(AddTol).Start(i);

        are.WaitOne();

        if (l.Count != amount ||
            l.Distinct().Count() != amount ||
            l.Min() < 0 ||
            l.Max() >= amount)
            throw new Exception("omg corrupted data");

        Console.WriteLine("All good");
        Console.ReadKey();
    }

    void AddTol(object o)
    {
        // uncomment to fix
        // lock (l) 
        l.Add((int)o);

        int i = Interlocked.Decrement(ref toFinish);

        if (i == 0)
            are.Set();
    }
}

@ ट्राय क्या आप इसे सिंगल कोर मशीन पर चला रहे हैं?
— बास स्मिट

नमस्ते, मुझे लगता है कि इस उदाहरण के साथ एक समस्या है क्योंकि यह जब भी यह 1000 नंबर पाता है AutoResetEvent की स्थापना कर रहा है। क्योंकि यह इन थ्रेड्स थोड़े को संसाधित कर सकता है जब भी यह चाहता है कि इसे 1000 तक मिल सकता है इससे पहले कि यह 999 के लिए उदाहरण के लिए हो जाए। यदि आप AddTol विधि में एक Console.WriteLine जोड़ते हैं तो आप देखेंगे कि क्रम क्रमबद्ध नहीं है।
— डेव वॉकर

@ याद रखें, इसकी घटना जब मैं == 0
— बास स्मिट

2

जैसा कि अन्य ने पहले ही कहा था, आप System.Collections.Concurrentनामस्थान से समवर्ती संग्रह का उपयोग कर सकते हैं । यदि आप उनमें से एक का उपयोग कर सकते हैं, तो यह पसंद किया जाता है।

लेकिन अगर आप वास्तव में एक सूची चाहते हैं, जो अभी सिंक्रनाइज़ है, तो आप SynchronizedCollection<T>-Class को देख सकते हैं System.Collections.Generic।

ध्यान दें कि आपको System.ServiceModel असेंबली को शामिल करना था, यही कारण है कि मुझे यह इतना पसंद नहीं है। लेकिन कभी-कभी मैं इसका इस्तेमाल करता हूं।


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