2010-07-29 16 views
17

मेरे पास foreach लूप है कि मैं समानांतर हूं और मैंने कुछ अजीब देखा है। कोड की तरहसमानांतर के साथ अलग-अलग सारांश परिणाम। फोरएच

double sum = 0.0; 

Parallel.ForEach(myCollection, arg => 
{ 
    sum += ComplicatedFunction(arg); 
}); 

// Use sum variable below 

लग रहा है जब मैं एक नियमित रूप से foreach पाश मैं अलग परिणाम प्राप्त का उपयोग करें। ComplicatedFunction के अंदर कुछ गहरा हो सकता है लेकिन यह संभव है कि sum चर समानांतरता से अप्रत्याशित रूप से प्रभावित हो रहा है?

+1

सी [ वेतन वृद्धि parallel.foreach गुंजाइश बाहर गिनती मूल्य] (http://stackoverflow.com/questions/2394447/increment-a-count-value-outside-parallel-foreach-scope)। असल में, आप [इंटरलॉक] (http://msdn.microsoft.com/en-us/library/55dzx06b.aspx) का उपयोग कर सकते हैं यदि आपको आवश्यकता हो, लेकिन यदि संभव हो तो साइड इफेक्ट्स से बचना बेहतर होता है। –

उत्तर

28

यह संभव है कि समांतर चर समानांतरता से अप्रत्याशित रूप से प्रभावित हो रहा है?

हां।
double तक पहुंच परमाणु नहीं है और sum += ... ऑपरेशन कभी थ्रेड-सुरक्षित नहीं है, न कि परमाणु वाले प्रकारों के लिए भी। तो आपके पास कई दौड़ की स्थिति है और परिणाम अप्रत्याशित है।

आप की तरह कुछ इस्तेमाल कर सकते हैं:

double sum = myCollection.AsParallel().Sum(arg => ComplicatedFunction(arg)); 

या, एक छोटा अंकन

double sum = myCollection.AsParallel().Sum(ComplicatedFunction); 
+0

यह काम किया। LINQ का उपयोग कर अच्छा साफ कार्यान्वयन। –

+0

मूल पोस्ट के अनुसार, क्या 'myCollection' के लिए थ्रेड-सुरक्षित होना जरूरी है? –

+0

@ केविन - नहीं, कोई भी 'IENumerable ' करेगा। –

4

में आपको लगता है कि sum += ComplicatedFunction के रूप में वास्तव में संचालन का एक समूह से बना जा रहा है के बारे में सोचते हैं, तो कहते हैं:

r1 <- Load current value of sum 
r2 <- ComplicatedFunction(...) 
r1 <- r1 + r2 

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

+5

आप सही हैं, लेकिन स्थिति वास्तव में आपके राज्य की तुलना में बहुत खराब है। यह सिर्फ इतना नहीं है कि लोड, गणना और स्टोर संचालन परमाणु नहीं हैं। एक बिट में * बिट्स * तक पहुंचने पर भी परमाणु होने की गारंटी नहीं है! सी # विनिर्देश केवल गारंटी देता है कि 32 बिट (और छोटे) संख्यात्मक प्रकारों और संदर्भों तक पहुंच परमाणु हैं। डबल्स 64 बिट हैं और इसलिए परमाणु होने की गारंटी नहीं है। इस कार्यक्रम को इस प्रकार महसूस किया जा सकता है: आर 1 <- लोड 32 बिट्स योग, आर 1 <लोड लोड 32 बिट्स योग ... जिसका मतलब है कि संचालन को अंतःस्थापित किया जा सकता है जबकि डबल के आधे * की प्रतिलिपि बनाई गई है। –

+0

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

11

अन्य उत्तरों की तरह उल्लेख किया गया है, sum एकाधिक धागे से चर (जो समानांतर है। फोरएच करता है) को थ्रेड-सुरक्षित ऑपरेशन नहीं है। अद्यतन करने से पहले लॉक प्राप्त करने का मामूली फिक्स को समस्या ठीक करेगा।

double sum = 0.0; 
Parallel.ForEach(myCollection, arg => 
{ 
    lock (myCollection) 
    { 
    sum += ComplicatedFunction(arg); 
    } 
}); 

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

अब, यह अधिकार प्राप्त करने की चाल अलग-अलग और स्वतंत्र चक्स में समस्या को विभाजित करना है। सौभाग्य से यह करना बहुत आसान है जब आप जो करना चाहते हैं वह पुनरावृत्तियों का परिणाम है क्योंकि योग ऑपरेशन कम्यूटेटिव और सहयोगी है और क्योंकि पुनरावृत्तियों के मध्यवर्ती परिणाम स्वतंत्र हैं।

तो यहां यह है कि आप इसे कैसे करते हैं।

double sum = 0.0; 
Parallel.ForEach(myCollection, 
    () => // Initializer 
    { 
     return 0D; 
    }, 
    (item, state, subtotal) => // Loop body 
    { 
     return subtotal += ComplicatedFunction(item); 
    }, 
    (subtotal) => // Accumulator 
    { 
     lock (myCollection) 
     { 
      sum += subtotal; 
     } 
    }); 
+1

आप एक बहुत ही मानक पहिया के पुनर्विचार को प्रोत्साहित क्यों कर रहे हैं? – Novelocrat

+0

@ नोवेलोक्रेट: क्षमा करें, मैं जो पूछ रहा हूं उस पर मैं स्पष्ट नहीं हूं। साथ ही, समय के कारण मुझे लगता है कि आप इस जवाब को कम कर चुके हैं? यदि उत्तर का कौन सा हिस्सा गलत है? मैंने कोड सिंटैक्स को दो बार चेक किया है और विभाजन रणनीति 'समांतर' के लिए 'ऑपरेशंस' करने के लिए एक बहुत अच्छी तरह से स्थापित अभ्यास है, लेकिन हो सकता है कि मैंने आपकी आंख को पकड़ा कुछ ऐसा याद किया। –

+0

जिस चीज का आप वर्णन करते हैं वह वास्तव में लाइब्रेरी फ़ंक्शन है जो हेनक के उत्तर का वर्णन करता है। इसके अलावा, मुझे दृढ़ता से संदेह है कि लाइब्रेरी कार्यान्वयन में प्रत्येक थ्रेड के उप-योग ('Accumulator') में कमी आपके लॉक-आधारित दृष्टिकोण से कहीं अधिक कुशल है। – Novelocrat

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