2012-05-11 29 views
7

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

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

इस बीच में मैं इंटरनेट खोजना जारी रखूंगा और अगर मुझे समाधान मिल जाए तो वापस पोस्ट करें।

संपादित करें: मुझे बताएं कि मुझे चीजों को साफ़ करने के लिए कुछ कोड पोस्ट करना चाहिए, लेकिन मैं इसे और अधिक स्पष्ट रूप से समझाऊंगा।

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

मुझे उस थ्रेड की आवश्यकता है जो प्रक्रिया के तब तक उपज करने के लिए नए तत्व जोड़ता है, क्रियाएँ निष्पादन विधि निष्पादित की जाती है।

  • किसी को भी इस समस्या पर ठोकर खाने के लिए। स्वीकृत उत्तर आपको एक त्वरित, आसान समाधान देगा, लेकिन अधिक विस्तृत उत्तर के लिए ब्रायन गिडियन का उत्तर नीचे देखें, जो आपको निश्चित रूप से अधिक अंतर्दृष्टि देगा!
+5

आप गलत हैं। ताले * बिल्कुल * आपको क्या चाहिए। –

+0

यह ठीक है (इसलिए "शायद मैं गलत हूं")। धन्यवाद हालांकि, मैं इसे तब तक रखूंगा जब तक कि मुझे यह पता न लगे कि इसे कैसे लागू किया जाए। समाधान – Denzil

+2

प्रदान करने के लिए आपको +1 +1 आपको सूची थ्रेड-सुरक्षित बनाने की आवश्यकता है, न कि 'ए' विधि। और उस से पूछा गया है और कई बार जवाब दिया गया है। –

उत्तर

17

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

लॉक सब कुछ

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

LinkedList<object> collection = new LinkedList<object>(); 

void Write() 
{ 
    lock (collection) 
    { 
    collection.AddLast(GetSomeObject()); 
    } 
} 

void Read() 
{ 
    lock (collection) 
    { 
    foreach (object item in collection) 
    { 
     DoSomething(item); 
    } 
    } 
} 

कॉपी-पढ़ें पैटर्न

यह एक थोड़ा और अधिक जटिल पैटर्न है। आप देखेंगे कि डेटा संरचना की एक प्रति इसे पढ़ने से पहले बनाई गई है। यह पैटर्न अच्छी तरह से काम करता है जब पढ़ने के संचालन की संख्या लिखने की संख्या की तुलना में कम होती है और प्रतिलिपि की गणना अपेक्षाकृत छोटी होती है।

LinkedList<object> collection = new LinkedList<object>(); 

void Write() 
{ 
    lock (collection) 
    { 
    collection.AddLast(GetSomeObject()); 
    } 
} 

void Read() 
{ 
    LinkedList<object> copy; 
    lock (collection) 
    { 
    copy = new LinkedList<object>(collection); 
    } 
    foreach (object item in copy) 
    { 
    DoSomething(item); 
    } 
} 

कॉपी-संशोधित-स्वैप पैटर्न

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

object lockobj = new object(); 
volatile LinkedList<object> collection = new LinkedList<object>(); 

void Write() 
{ 
    lock (lockobj) 
    { 
    var copy = new LinkedList<object>(collection); 
    copy.AddLast(GetSomeObject()); 
    collection = copy; 
    } 
} 

void Read() 
{ 
    LinkedList<object> local = collection; 
    foreach (object item in local) 
    { 
    DoSomething(item); 
    } 
} 

अद्यतन:

तो मैं टिप्पणी अनुभाग में दो सवाल पूछे:

  • क्यों lock(lockobj) बजाय lock(collection) लिखने की ओर?
  • रीड साइड पर local = collection क्यों?

पहले प्रश्न के बारे में विचार करें कि सी # कंपाइलर lock का विस्तार कैसे करेगा।

void Write() 
{ 
    bool acquired = false; 
    object temp = lockobj; 
    try 
    { 
    Monitor.Enter(temp, ref acquired); 
    var copy = new LinkedList<object>(collection); 
    copy.AddLast(GetSomeObject()); 
    collection = copy; 
    } 
    finally 
    { 
    if (acquired) Monitor.Exit(temp); 
    } 
} 

अब उम्मीद है कि यह क्या गलत हो सकता है अगर हम ताला अभिव्यक्ति के रूप में इस्तेमाल किया collection देखने के लिए आसान है।

  • थ्रेड ए object temp = collection निष्पादित करता है।
  • थ्रेड बी collection = copy निष्पादित करता है।
  • थ्रेड सी object temp = collection निष्पादित करता है।
  • थ्रेड ए मूल संदर्भ के साथ लॉक प्राप्त करता है।
  • थ्रेड सी नया संदर्भ के साथ लॉक प्राप्त करता है।

स्पष्ट रूप से यह आपदाजनक होगा! महत्वपूर्ण अनुभाग एक से अधिक बार दर्ज होने के बाद से लिखा जाएगा।

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

void Read() 
{ 
    object x = collection.Last; 
    // The collection may get swapped out right here. 
    object y = collection.Last; 
    if (x != y) 
    { 
    Console.WriteLine("It could happen!"); 
    } 
} 

समस्या है कि यहाँ collection किसी भी समय बदली हो सकता है। यह अविश्वसनीय रूप से खोजने के लिए मुश्किल बग होगा। यही कारण है कि इस पैटर्न को करते समय मैं हमेशा पढ़ने के पक्ष में स्थानीय संदर्भ निकालता हूं। यह सुनिश्चित करता है कि हम प्रत्येक पढ़ने के ऑपरेशन पर समान संग्रह का उपयोग कर रहे हैं।

फिर, क्योंकि इस तरह की समस्याओं इतना सूक्ष्म हैं मैं जब तक आप वास्तव में करने की जरूरत है इस पद्धति का उपयोग कर सलाह नहीं देते।

+0

प्रश्नोत्तरी ... क्या कोई अनुमान लगा सकता है क्यों 1) मैंने लिखने के पक्ष में 'लॉक (संग्रह)' के बजाय 'लॉक (लॉकबोज)' का उपयोग किया और 2) स्थानीय संदर्भ का उपयोग ' "कॉपी, संशोधित, स्वैप" पैटर्न में पढ़ने के पक्ष में स्थानीय = संग्रह'? –

+0

मैं इसे बहुत विस्तार से पढ़ूंगा और आशा करता हूं कि आप अपने प्रश्नोत्तरी का उत्तर दें: पी – Denzil

+0

मैं ओपी के लिए प्रश्नोत्तरी का उत्तर देने का मजा छोड़ दूंगा :-) त्वरित तकनीकी प्रश्न: क्या अस्थिर वास्तव में आवश्यक है?लॉक स्टेटमेंट एक पूर्ण बाड़ की गारंटी नहीं देता है? – Douglas

5

यहाँ कैसे सूची तक आपकी पहुंच को सिंक्रनाइज़ करने के लिए उपयोग करने के लिए ताले की एक त्वरित उदाहरण है:

private readonly IList<string> elements = new List<string>(); 

public void ProcessElements() 
{ 
    lock (this.elements) 
    { 
     foreach (string element in this.elements) 
      ProcessElement(element); 
    } 
} 

public void AddElement(string newElement) 
{ 
    lock (this.elements) 
    { 
     this.elements.Add(element); 
    } 
} 

एक lock(o) बयान को क्रियान्वित करने का मतलब है कि धागा वस्तु o पर एक आपसी-बहिष्कार ताला प्राप्त करना चाहिए, कथन ब्लॉक निष्पादित करें, और अंततः o पर लॉक जारी करें। यदि कोई अन्य थ्रेड o पर एक लॉक प्राप्त करने का प्रयास करता है (या तो एक ही कोड ब्लॉक या किसी अन्य के लिए), तो यह लॉक जारी होने तक (प्रतीक्षा) ब्लॉक करेगा।

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

private IList<string> elements = new List<string>(); 

private readonly object syncLock = new object(); 

public void ProcessElements() 
{ 
    lock (this.syncLock) 
    { 
     foreach (string element in this.elements) 
      ProcessElement(element); 
    } 
} 

public void AddElement(string newElement) 
{ 
    lock (this.syncLock) 
    { 
     this.elements.Add(element); 
    } 
} 
+0

ओह वाह जो वास्तव में सरल दिखता है। मेरे द्वारा देखे गए सभी उदाहरणों में ऑब्जेक्ट ओ = नया ऑब्जेक्ट() था, और फिर वे इसे लॉक कर देंगे और यह मुझे भ्रमित कर देगा। लेकिन मुझे लगता है कि वे एक सामान्य उदाहरण दे रहे थे। यह बहुत दोस्त है! – Denzil

+1

@Denzil आपको कुछ निजी (निजी महत्वपूर्ण है) फ़ील्ड चाहिए जो लॉक करने के लिए किसी भी अन्य कोड बेस तक पहुंच योग्य नहीं है। (यदि यह कहीं और सुलभ है और आप इसके लिए तैयार नहीं हैं तो आपको डेडलॉक्स प्राप्त करने की अधिक संभावना है।) अक्सर उद्देश्य के लिए कोई वस्तु उपयुक्त नहीं होती है, इसलिए लोग एक नई वस्तु बनाते हैं (इसमें कोई फ़ील्ड नहीं है, इसलिए यह उठता है सबसे छोटी संभव स्मृति पदचिह्न) बस लॉक करने के प्रयोजनों के लिए। अधिकांश समय यह सबसे अच्छा है; किसी और चीज पर लॉक करना अक्सर एक बग होने का इंतजार कर रहा है। – Servy

+1

@Denzil: अंगूठे के नियम के रूप में, आप किसी भी क्षेत्र का उपयोग कर सकते हैं जिसे निजी * और * केवल पढ़ने के रूप में घोषित किया जाता है। यदि आप पहले से ही एक का उपयोग कर रहे हैं (जैसे कि पहले उदाहरण में 'तत्व' संग्रह), तो आप जाने के लिए अच्छे हैं। यदि नहीं, विशेष रूप से उद्देश्य के लिए एक नई वस्तु बनाएँ। – Douglas

संबंधित मुद्दे