परिचय
आमतौर पर, मल्टी-कोर कंप्यूटर के लिए एक कार्यक्रम का अनुकूलन करते समय, पहला चरण यह पता लगाना है कि एल्गोरिथ्म को समानांतर में चलने वाले भागों में विभाजित किया जा सकता है या नहीं। यदि समस्या को हल करने के लिए समानांतर में सेट किए गए एक बड़े डेटा से अलग तत्वों को संसाधित करना आवश्यक है, तो पहले उम्मीदवार .NET फ्रेमवर्क 4 में नए समानांतरवाद विशेषताएं होंगे:
Parallel.ForEach और Parallel LINQ (
PLINQ )
Parallel.ForEach
समानांतर श्रेणी में
ForEach पद्धति शामिल है, जो C # में एक नियमित रूप से foreach लूप का एक मल्टीथ्रेडेड संस्करण है। नियमित रूप से फॉर्च्यूनर की तरह, Parallel.ForEach असंख्य डेटा पर पुनरावृत्त करता है, लेकिन कई थ्रेड का उपयोग करके। अधिक सामान्यतः उपयोग किए जाने वाले
Parallel.ForEach ओवरलोड में से एक निम्नानुसार है:
public static ParallelLoopResult ForEach<TSource>( IEnumerable<TSource> source, Action<TSource> body)
Ienumerable अनुक्रम को पुनरावृत्त होने का संकेत देता है, और एक्शन बॉडी प्रत्येक तत्व के लिए प्रतिनिधि को सेट करती है। Parallel.ForEach ओवरलोड की पूरी सूची
यहां पाई जा सकती
है ।
PLINQ
Parallel.ForEach
PLINQ से संबंधित समानांतर डेटा ऑपरेशन के लिए एक प्रोग्रामिंग मॉडल है। उपयोगकर्ता ऑपरेटरों के एक मानक सेट से एक ऑपरेशन को परिभाषित करता है, जिसमें अनुमान, फिल्टर, एकत्रीकरण आदि शामिल हैं। Parallel.ForEach की तरह,
PLINQ विभिन्न अनुक्रमों में इनपुट अनुक्रम को भागों और प्रसंस्करण तत्वों में तोड़कर समानता प्राप्त करता है।
लेख इन दो दृष्टिकोणों के बीच अंतर को समानता पर प्रकाश डालता है। उपयोग परिदृश्यों को समझना जिसमें PLINQ के बजाय Parallel.ForEach का उपयोग करना सबसे अच्छा है और इसके विपरीत।
स्वतंत्र संचालन
यदि आपको अनुक्रम के तत्वों पर लंबी गणना करने की आवश्यकता है और परिणाम स्वतंत्र हैं, तो Parallel.FacEach का उपयोग करना बेहतर होगा। PLinq, बदले में, इस तरह के संचालन के लिए बहुत भारी होगा। इसके अतिरिक्त, थ्रेड्स की अधिकतम संख्या
Parallel.ForEach के लिए इंगित की जाती है, अर्थात, अगर
थ्रेडपूल के पास कुछ संसाधन हैं और
ParallelOptions.MaxDegreeOfParallelism में निर्दिष्ट थ्रेड्स की तुलना में बहुत कम थ्रेड उपलब्ध हैं, तो थ्रेड्स की अधिकतम संख्या का उपयोग किया जाएगा, जो इसे चलाते ही बढ़ सकता है।
PLINQ के लिए, निष्पादित करने के लिए थ्रेड्स
की संख्या सख्ती से निर्दिष्ट है।
डेटा ऑर्डर संरक्षण के साथ समानांतर संचालन
आदेश रखने के लिए PLINQ
यदि आपके रूपांतरणों को इनपुट ऑर्डर को संरक्षित करने की आवश्यकता होती है, तो आप
समानांतर रूप से
PLINQ का उपयोग करना आसान
पाएंगे । उदाहरण के लिए, यदि हम आउटपुट पर आरजीबी रंग वीडियो फ्रेम को काले और सफेद में बदलना चाहते हैं, तो फ्रेम ऑर्डर स्वाभाविक रूप से संरक्षित होना चाहिए। इस स्थिति में,
PLINQ और
AsOrdered () फ़ंक्शन का उपयोग करना बेहतर होता है, जो PLINQ विभाजन की गहराई में इनपुट अनुक्रम करता है, रूपांतरण करता है, और फिर सही क्रम में परिणाम की व्यवस्था करता है।
public static void GrayscaleTransformation(IEnumerable<Frame> Movie) { var ProcessedMovie = Movie .AsParallel() .AsOrdered() .Select(frame => ConvertToGrayscale(frame)); foreach (var grayscaleFrame in ProcessedMovie) {
यहां Parallel.ForEach का उपयोग क्यों नहीं किया गया?
तुच्छ मामलों को छोड़कर, Parallel.ForEach का उपयोग करके सीरियल डेटा पर समानांतर संचालन को लागू करने के लिए कोड की एक महत्वपूर्ण राशि की आवश्यकता होती है। हमारे मामले में, हम AsOrdered () ऑपरेटर के प्रभाव को दोहराने के लिए Foreach फ़ंक्शन के अधिभार का उपयोग कर सकते हैं:
public static ParallelLoopResult ForEach<TSource >( IEnumerable<TSource> source, Action<TSource, ParallelLoopState,Int64>body)
Foreach के ओवरलोड संस्करण में , डेटा एक्शन डेलिगेट में वर्तमान तत्व के सूचकांक पैरामीटर को जोड़ा गया है। अब हम एक ही सूचकांक में आउटपुट संग्रह के लिए परिणाम लिख सकते हैं, समानांतर में महंगी गणना कर सकते हैं, और अंत में सही क्रम में आउटपुट अनुक्रम प्राप्त कर सकते हैं। निम्नलिखित उदाहरण Parallel.ForEach के साथ ऑर्डर बनाए रखने का एक तरीका दिखाता है:
public static double [] PairwiseMultiply( double[] v1, double[] v2) { var length = Math.Min(v1.Length, v2.Lenth); double[] result = new double[length]; Parallel.ForEach(v1, (element, loopstate, elementIndex) => result[elementIndex] = element * v2[elementIndex]); return result; }
हालांकि, इस दृष्टिकोण की कमियों को तुरंत खोजा जाता है। यदि इनपुट अनुक्रम एक IEnumerable प्रकार है और एक सरणी नहीं है, तो आदेश संरक्षण को लागू करने के 4 तरीके हैं:
- पहला विकल्प IEnumerable.Count () को कॉल करना है, जिसकी लागत O (n) होगी। यदि तत्वों की संख्या ज्ञात है, तो आप किसी दिए गए इंडेक्स पर परिणामों को बचाने के लिए एक आउटपुट सरणी बना सकते हैं
- दूसरा विकल्प संग्रह को भौतिक बनाना है (इसे मोड़कर, उदाहरण के लिए, एक सरणी में)। यदि बहुत अधिक डेटा है, तो यह विधि बहुत उपयुक्त नहीं है।
- तीसरा विकल्प आउटपुट संग्रह के बारे में सावधानी से सोचना है। आउटपुट संग्रह एक हैश हो सकता है, फिर आउटपुट मान को संग्रहीत करने के लिए आवश्यक मेमोरी की मात्रा हैश टकराव से बचने के लिए इनपुट मेमोरी से कम से कम 2 गुना होगी; यदि बहुत अधिक डेटा है, तो हैश के लिए डेटा संरचना निषेधात्मक रूप से बड़ी होगी, इसके अलावा, आप गलत साझाकरण और कचरा संग्रहकर्ता के कारण एक प्रदर्शन ड्रॉप प्राप्त कर सकते हैं।
- और अंतिम विकल्प उनके मूल अनुक्रमित के साथ परिणामों को सहेजना है, और फिर आउटपुट संग्रह को सॉर्ट करने के लिए अपना स्वयं का एल्गोरिदम लागू करना है।
PLINQ में, उपयोगकर्ता केवल आदेश के संरक्षण के लिए पूछता है, और क्वेरी इंजन परिणामों के सही क्रम को सुनिश्चित करने के सभी नियमित विवरण का प्रबंधन करता है। PLINQ फ्रेमवर्क असोएरेटेड () ऑपरेटर को स्ट्रीमिंग डेटा को संसाधित करने की अनुमति देता है, दूसरे शब्दों में, PLINQ आलसी भौतिककरण का समर्थन करता है। PLINQ में, पूरे अनुक्रम को भौतिक रूप से बदलना सबसे बुरा समाधान है, आप आसानी से उपरोक्त समस्याओं से बच सकते हैं और डेटा पर समानांतर संचालन कर सकते हैं बस एसरोडर्ड () ऑपरेटर का उपयोग कर सकते हैं ।
समानांतर स्ट्रीमिंग
एक स्ट्रीम को संसाधित करने के लिए PLINQ का उपयोग करना
PLINQ एक स्ट्रीम पर अनुरोध के रूप में अनुरोध को संसाधित करने की क्षमता प्रदान करता है। यह सुविधा निम्नलिखित कारणों से अत्यंत मूल्यवान है:
- 1. परिणाम सरणी में नहीं चलते हैं, इसलिए मेमोरी में डेटा संग्रहीत करने में कोई अतिरेक नहीं है।
- 2. जब आप नया डेटा प्राप्त करते हैं तो आप गणना की एक ही धारा में परिणाम की गणना कर सकते हैं।
प्रतिभूतियों के विश्लेषण के उदाहरण के साथ जारी रखते हुए, कल्पना करें कि आप प्रतिभूतियों के एक पोर्टफोलियो से प्रत्येक पेपर के जोखिम की गणना करना चाहते हैं, केवल प्रतिभूतियों को दे रहे हैं जो जोखिम विश्लेषण के मानदंडों को पूरा करते हैं, और फिर फ़िल्टर किए गए परिणामों पर कुछ गणना करते हैं। PLINQ में, कोड कुछ इस तरह दिखाई देगा:
public static void AnalyzeStocks(IEnumerable<Stock> Stocks) { var StockRiskPortfolio = Stocks .AsParallel() .AsOrdered() .Select(stock => new { Stock = stock, Risk = ComputeRisk(stock)}) .Where(stockRisk => ExpensiveRiskAnalysis(stockRisk.Risk)); foreach (var stockRisk in StockRiskPortfolio) { SomeStockComputation(stockRisk.Risk);
इस उदाहरण में, तत्वों को भागों ( विभाजन ) में वितरित किया जाता है, जिसे कई थ्रेड्स द्वारा संसाधित किया जाता है, फिर पुन: व्यवस्थित किया जाता है; यह समझना महत्वपूर्ण है कि ये चरण समानांतर में किए जाते हैं, जैसा कि फ़िल्टरिंग परिणाम दिखाई देते हैं, फ़ॉरच लूप में एक-थ्रेडेड उपभोक्ता गणना कर सकता है। PLINQ प्रदर्शन के लिए अनुकूलित है, विलंबता नहीं, और आंतरिक रूप से बफ़र्स का उपयोग करता है; ऐसा हो सकता है कि यद्यपि एक आंशिक परिणाम पहले ही प्राप्त हो चुका है, यह आउटपुट बफर में रहेगा जब तक कि आउटपुट बफर पूरी तरह से संतृप्त नहीं होता है और आगे की प्रक्रिया की अनुमति नहीं देता है। PLINQ WithMergeOptions एक्सटेंशन विधि का उपयोग करके स्थिति को ठीक किया जा सकता है, जो आपको आउटपुट बफ़रिंग निर्दिष्ट करने की अनुमति देता है। WithMergeOptions विधि एक पैरामीटर के रूप में ParallelMergeOptions गणन लेता है; आप निर्दिष्ट कर सकते हैं कि क्वेरी अंतिम परिणाम कैसे लौटाती है जिसका उपयोग एकल स्ट्रीम द्वारा किया जाएगा। निम्नलिखित विकल्पों की पेशकश कर रहे हैं:
- ParallelMergeOptions.NotBuffered - इंगित करता है कि प्रत्येक संसाधित आइटम को संसाधित होते ही प्रत्येक थ्रेड से वापस कर दिया जाता है।
- ParallelMergeOptions.AutoBuffered - इंगित करता है कि तत्वों को बफर में एकत्र किया जाता है, बफर समय-समय पर उपभोक्ता स्ट्रीम में वापस आ जाता है
- ParallelMergeOptions.FullyBuffered - इंगित करता है कि आउटपुट अनुक्रम पूरी तरह से बफ़र है, यह आपको अन्य विकल्पों का उपयोग करने की तुलना में तेज़ी से परिणाम प्राप्त करने की अनुमति देता है, लेकिन फिर उपभोक्ता थ्रेड को प्रसंस्करण के लिए पहला तत्व प्राप्त करने के लिए लंबा इंतजार करना होगा।
UsingMergeOptions उदाहरण MSDN पर उपलब्ध है
समानांतर क्यों नहीं।फोर ईच?
अनुक्रम के क्रम को बनाए रखने के लिए Parallel.ForEach की कमियों को अलग रखें। Parallel.ForEach का उपयोग करके किसी स्ट्रीम पर अनियंत्रित संगणना के लिए, कोड इस तरह दिखेगा:
public static void AnalyzeStocks(IEnumerable<Stock> Stocks) { Parallel.ForEach(Stocks, stock => { var risk = ComputeRisk(stock); if(ExpensiveRiskAnalysis(risk) {
यह कोड PLINQ उदाहरण के लगभग समान है, जिसमें स्पष्ट अवरुद्ध और कम सुरुचिपूर्ण कोड के अपवाद हैं। ध्यान दें कि इस स्थिति में, Parallel.ForeEach का अर्थ है कि परिणाम को थ्रेड-सुरक्षित शैली में संग्रहीत करना, जबकि PLINQ यह आपके लिए करता है।
परिणामों को बचाने के लिए, हमारे पास 3 तरीके हैं: पहला है स्ट्रीम-असुरक्षित संग्रह में मूल्यों को सहेजना और प्रत्येक रिकॉर्ड पर लॉक की आवश्यकता। दूसरा इसे थ्रेड-सुरक्षित संग्रह में सहेजना है, सौभाग्य से, .NET फ्रेमवर्क 4 System.Collections.Concurrent नामस्थान में इस तरह के संग्रह का एक सेट प्रदान करता है और आपको इसे स्वयं लागू नहीं करना होगा। तीसरा तरीका थ्रेड-लोकल स्टोरेज के साथ Parallel.ForEach का उपयोग करना है, जिसे बाद में वर्णित किया जाएगा। इन तरीकों में से प्रत्येक को संग्रह में लेखन के तीसरे पक्ष के प्रभावों के स्पष्ट नियंत्रण की आवश्यकता है, जबकि PLINQ हमें इन कार्यों से अमूर्त करने की अनुमति देता है।
दो संग्रह पर संचालन
दो संग्रहों पर संचालन के लिए PLINQ का उपयोग करना
PLINQ ज़िप ऑपरेटर विशेष रूप से दो अलग-अलग संग्रहों पर समानांतर गणना करता है। चूंकि इसे अन्य प्रश्नों के साथ जोड़ा जा सकता है, आप दो संग्रह को संयोजित करने से पहले प्रत्येक संग्रह पर एक साथ जटिल संचालन कर सकते हैं। उदाहरण के लिए:
public static IEnumerable<T> Zipping<T>(IEnumerable<T> a, IEnumerable<T> b) { return a .AsParallel() .AsOrdered() .Select(element => ExpensiveComputation(element)) .Zip( b .AsParallel() .AsOrdered() .Select(element => DifferentExpensiveComputation(element)), (a_element, b_element) => Combine(a_element,b_element)); }
ऊपर दिए गए उदाहरण से पता चलता है कि प्रत्येक डेटा स्रोत को अलग-अलग ऑपरेशन के समानांतर कैसे संसाधित किया जाता है, फिर दोनों स्रोतों से परिणाम ज़िप ऑपरेटर द्वारा संयुक्त होते हैं।
समानांतर क्यों नहीं।फोर ईच?
इसी तरह के ऑपरेशन को Parallel.ForEach अधिभार के साथ अनुक्रमणिकाओं का उपयोग करके किया जा सकता है, उदाहरण के लिए:
public static IEnumerable<T> Zipping<T>(IEnumerable<T> a, IEnumerable<T> b) { var numElements = Math.Min(a.Count(), b.Count()); var result = new T[numElements]; Parallel.ForEach(a, (element, loopstate, index) => { var a_element = ExpensiveComputation(element); var b_element = DifferentExpensiveComputation(b.ElementAt(index)); result[index] = Combine(a_element, b_element); }); return result; }
हालाँकि, Parallel.ForEach के अनुप्रयोग में डेटा ऑर्डर के संरक्षण के साथ संभावित जाल और कमियाँ वर्णित हैं, जिनमें से एक नुकसान में संपूर्ण संग्रह को अंतिम और स्पष्ट सूचकांक प्रबंधन को देखना शामिल है।
धागा-स्थानीय राज्य
ParallelForEach का उपयोग करके स्ट्रीम की स्थानीय स्थिति तक पहुँच प्राप्त करें
यद्यपि PLINQ डेटा पर समानांतर संचालन के लिए अधिक संक्षिप्त साधन प्रदान करता है, कुछ प्रसंस्करण परिदृश्य Parallel.ForEach का उपयोग करने के लिए बेहतर अनुकूल हैं, उदाहरण के लिए, ऐसे ऑपरेशन जो किसी स्ट्रीम की स्थानीय स्थिति का समर्थन करते हैं। इसी Parallel.ForEach विधि के हस्ताक्षर इस तरह दिखता है:
public static ParallelLoopResult ForEach<TSource,TLocal>( IEnumerable<TSource> source, Func<TLocal> localInit, Func<TSource, ParallelLoopState, TLocal, TLocal> body, Action<TLocal> localFinally)
यह ध्यान दिया जाना चाहिए कि एग्रीगेट ऑपरेटर का एक अधिभार है, जो स्ट्रीम की स्थानीय स्थिति तक पहुंच की अनुमति देता है और इसका उपयोग किया जा सकता है यदि डेटा प्रोसेसिंग टेम्पलेट को आयाम में कमी के रूप में वर्णित किया जा सकता है। निम्न उदाहरण दिखाता है कि उन संख्याओं को कैसे हटाया जाए जो किसी अनुक्रम से अभाज्य नहीं हैं:
public static List<R> Filtering<T,R>(IEnumerable<T> source) { var results = new List<R>(); using (SemaphoreSlim sem = new SemaphoreSlim(1)) { Parallel.ForEach(source, () => new List<R>(), (element, loopstate, localStorage) => { bool filter = filterFunction(element); if (filter) localStorage.Add(element); return localStorage; }, (finalStorage) => { lock(myLock) { results.AddRange(finalStorage) }; }); } return results; }
इस तरह की कार्यक्षमता को PLINQ के साथ अधिक आसानी से प्राप्त किया जा सकता है, उदाहरण का उद्देश्य यह दिखाना है कि Parallel.ForEach और स्ट्रीम की स्थानीय स्थिति का उपयोग करके सिंक्रनाइज़ेशन लागत को बहुत कम किया जा सकता है। हालांकि, अन्य परिदृश्यों में, स्थानीय प्रवाह राज्य बिल्कुल आवश्यक हो जाते हैं; निम्नलिखित उदाहरण इस तरह के परिदृश्य को प्रदर्शित करता है।
कल्पना कीजिए कि एक शानदार कंप्यूटर वैज्ञानिक और गणितज्ञ के रूप में, आपने प्रतिभूतियों के जोखिमों के विश्लेषण के लिए एक सांख्यिकीय मॉडल विकसित किया है; यह मॉडल आपको लगता है कि अन्य सभी जोखिम मॉडल को तोड़ देगा। इसे साबित करने के लिए, आपको शेयर बाजारों के बारे में जानकारी वाली साइटों से डेटा की आवश्यकता है। लेकिन डेटा अनुक्रम लोड करना बहुत लंबा होगा और आठ-कोर कंप्यूटर के लिए एक अड़चन है। हालाँकि Parallel.ForEach का उपयोग करना WebClient का उपयोग करके समानांतर में डेटा लोड करने का एक आसान तरीका है, प्रत्येक थ्रेड को डाउनलोड होने पर हर बार ब्लॉक किया जाएगा, जो अतुल्यकालिक I / O के उपयोग में सुधार कर सकता है; अधिक जानकारी यहाँ उपलब्ध है । प्रदर्शन कारणों से, आपने Parallel.ForEach का उपयोग URL के संग्रह के माध्यम से पुनरावृत्त करने और समानांतर में डेटा अपलोड करने के लिए किया। कोड कुछ इस तरह दिखता है:
public static void UnsafeDownloadUrls () { WebClient webclient = new WebClient(); Parallel.ForEach(urls, (url,loopstate,index) => { webclient.DownloadFile(url, filenames[index] + ".dat"); Console.WriteLine("{0}:{1}", Thread.CurrentThread.ManagedThreadId, url); }); }
आश्चर्यजनक रूप से, हमें रनटाइम पर एक अपवाद मिलता है: "System.NotSupportedException -> WebClient समवर्ती I / O संचालन का समर्थन नहीं करता है।" यह महसूस करते हुए कि कई धागे एक ही समय में एक ही WebClient का उपयोग नहीं कर सकते, आप एक WebClient बनाने का निर्णय लेते हैं। हर डाउनलोड के लिए।
public static void BAD_DownloadUrls () { Parallel.ForEach(urls, (url,loopstate,index) => { WebClient webclient = new WebClient(); webclient.DownloadFile(url, filenames[index] + ".dat"); Console.WriteLine("{0}:{1}", Thread.CurrentThread.ManagedThreadId, url); }); }
यह कोड प्रोग्राम को सौ से अधिक वेब क्लाइंट बनाने की अनुमति देता है; यह प्रोग्राम वेबक्लिक में टाइमआउट अपवाद को फेंक देगा। आप समझेंगे कि कंप्यूटर एक सर्वर ऑपरेटिंग सिस्टम नहीं चला रहा है, इसलिए कनेक्शन की अधिकतम संख्या सीमित है। तब आप अनुमान लगा सकते हैं कि धारा की स्थानीय स्थिति के साथ Parallel.ForEach का उपयोग करने से समस्या हल हो जाएगी:
public static void downloadUrlsSafe() { Parallel.ForEach(urls, () => new WebClient(), (url, loopstate, index, webclient) => { webclient.DownloadFile(url, filenames[index]+".dat"); Console.WriteLine("{0}:{1}", Thread.CurrentThread.ManagedThreadId, url); return webclient; }, (webclient) => { }); } }
इस कार्यान्वयन में, प्रत्येक डेटा एक्सेस ऑपरेशन दूसरे से स्वतंत्र है। इसी समय, पहुंच बिंदु न तो स्वतंत्र है और न ही थ्रेड सुरक्षित है। स्थानीय स्ट्रीम स्टोरेज का उपयोग करने से हमें यह सुनिश्चित करने की अनुमति मिलती है कि बनाए गए WebClient इंस्टेंस की संख्या जितनी आवश्यक हो, और प्रत्येक WebClient इंस्टेंस उस स्ट्रीम के अंतर्गत आता है जिसने इसे बनाया है।
यहाँ PLINQ खराब क्यों है?
यदि आप थ्रेडलोक और PLINQ ऑब्जेक्ट का उपयोग करके पिछले उदाहरण को लागू करते हैं, तो कोड निम्नानुसार है:
public static void downloadUrl() { var webclient = new ThreadLocal<WebClient>(()=> new WebClient ()); var res = urls .AsParallel() .ForAll( url => { webclient.Value.DownloadFile(url, host[url] +".dat")); Console.WriteLine("{0}:{1}", Thread.CurrentThread.ManagedThreadId, url); }); }
हालांकि कार्यान्वयन समान लक्ष्यों को प्राप्त करता है, यह समझना महत्वपूर्ण है कि किसी भी परिदृश्य में, थ्रेडलोकल <> का उपयोग करना समानांतर Par.ForEach अधिभार की तुलना में काफी अधिक महंगा है। ध्यान दें कि इस परिदृश्य में, इंटरनेट से फ़ाइल डाउनलोड करने में लगने वाले समय की तुलना में थ्रेडलोकल <> इंस्टेंस बनाने की लागत नगण्य है।
ऑपरेशन से बाहर निकलें
परिचालनों के बाहर निकलने के लिए Parallel.ForEach का उपयोग करना
ऐसी स्थिति में जहां संचालन के निष्पादन पर नियंत्रण आवश्यक है, यह समझना महत्वपूर्ण है कि समानांतर.फॉरइच चक्र से बाहर निकलना आपको चक्र के शरीर के अंदर कंप्यूटिंग जारी रखने की आवश्यकता के लिए शर्तों की जांच के समान प्रभाव प्राप्त करने की अनुमति देता है। Parallel.ForEach ओवरलोड में से एक, जो आपको ParallelLoopState को ट्रैक करने की अनुमति देता है:
public static ParallelLoopResult ForEach<TSource >( IEnumerable<TSource> source, Action<TSource, ParallelLoopState> body)
ParallelLoopState नीचे वर्णित दो अलग-अलग तरीकों से लूप निष्पादन को बाधित करने के लिए समर्थन प्रदान करता है।
ParallelLoopState.Stop ()
स्टॉप () पुनरावृत्तियों को रोकने की आवश्यकता के बारे में लूप को सूचित करता है; ParallelLoopState.IsStopping संपत्ति प्रत्येक पुनरावृत्ति को यह निर्धारित करने की अनुमति देती है कि क्या कुछ अन्य पुनरावृत्ति ने स्टॉप () विधि को बुलाया है। स्टॉप () विधि आमतौर पर उपयोगी होती है यदि लूप एक अनियंत्रित खोज करता है और आइटम मिलते ही बाहर निकल जाना चाहिए। उदाहरण के लिए, यदि हम यह जानना चाहते हैं कि कोई वस्तु संग्रह में मौजूद है, तो कोड कुछ इस तरह दिखाई देगा:
public static boolean FindAny<T,T>(IEnumerable<T> TSpace, T match) where T: IEqualityComparer<T> { var matchFound = false; Parallel.ForEach(TSpace, (curValue, loopstate) => { if (curValue.Equals(match) ) { matchFound = true; loopstate.Stop(); } }); return matchFound; }
PLINQ का उपयोग करके कार्यक्षमता को भी प्राप्त किया जा सकता है, यह उदाहरण दर्शाता है कि निष्पादन के प्रवाह को नियंत्रित करने के लिए ParallelLoopState.Stop () का उपयोग कैसे करें।
ParallelLoopState.Break ()
ब्रेक () लूप को सूचित करता है कि वर्तमान तत्व से पहले के तत्वों को संसाधित किया जाना चाहिए, लेकिन पुनरावृत्ति के बाद के तत्वों के लिए इसे रोकना आवश्यक है। निचले पुनरावृत्ति मूल्य को ParallelLoopState.LowestBreakIteration प्रॉपर्टी से प्राप्त किया जा सकता है। यदि आप ऑर्डर किए गए डेटा के माध्यम से खोज कर रहे हैं तो ब्रेक () आमतौर पर उपयोगी है। दूसरे शब्दों में, डेटा प्रोसेसिंग की आवश्यकता के लिए एक निश्चित मानदंड है। उदाहरण के लिए, गैर-अद्वितीय तत्वों वाले अनुक्रम के लिए जिसमें एक मिलान वस्तु के निचले सूचकांक को खोजना आवश्यक है, कोड इस तरह दिखाई देगा:
public static int FindLowestIndex<T,T>(IEnumerable<T> TSpace, T match) where T: IEqualityComparer<T> { var loopResult = Parallel.ForEach(source, (curValue, loopState, curIndex) => { if (curValue.Equals(match)) { loopState.Break(); } }); var matchedIndex = loopResult.LowestBreakIteration; return matchedIndex.HasValue ? matchedIndex : -1; }
इस उदाहरण में, लूप को तब तक निष्पादित किया जाता है जब तक कि कोई ऑब्जेक्ट नहीं मिलता है, ब्रेक () सिग्नल का अर्थ है कि पाया गया ऑब्जेक्ट की तुलना में कम इंडेक्स वाले तत्वों को संसाधित किया जाना चाहिए; यदि दूसरा मिलान उदाहरण पाया जाता है, तो ब्रेक () सिग्नल फिर से प्राप्त होगा, यह तब तक दोहराया जाता है जब तक कि तत्व नहीं होते हैं, यदि ऑब्जेक्ट मिला था, तो LowestBreakIteration फ़ील्ड मिलान ऑब्जेक्ट के पहले सूचकांक को इंगित करता है।
PLINQ क्यों नहीं?
यद्यपि PLINQ क्वेरी निष्पादन को छोड़ने के लिए समर्थन प्रदान करता है, PLINQ और Parallel.ForEach के निकास तंत्र में अंतर महत्वपूर्ण हैं। PLINQ अनुरोध से बाहर निकलने के लिए, अनुरोध को रद्द टोकन के साथ प्रदान किया जाना चाहिए, जैसा कि यहां वर्णित है । C समानांतर । प्रत्येक निकास झंडे को प्रत्येक पुनरावृत्ति पर चुना जाता है। PLINQ के मामले में , आप जल्दी से रोकने के लिए रद्द किए गए अनुरोध पर भरोसा नहीं कर सकते।
निष्कर्ष
Parallel.ForEach और PLINQ अपने काम के तंत्र में गहरी विसर्जन की आवश्यकता के बिना अपने अनुप्रयोगों में जल्दी से संगामिति शुरू करने के लिए शक्तिशाली उपकरण हैं। हालांकि, किसी विशेष समस्या को हल करने के लिए सही उपकरण चुनने के लिए, इस लेख में वर्णित अंतर और युक्तियों को याद रखें।
उपयोगी लिंक:
C # में थ्रेडिंग
RSDN: C # में स्ट्रीम के साथ कार्य करें। समवर्ती प्रोग्रामिंग
.NET फ्रेमवर्क के साथ समानांतर प्रोग्रामिंग के लिए Microsoft नमूने