.NET - शब्दकोश लॉकिंग बनाम समवर्ती छायांकन


131

मैं ConcurrentDictionaryप्रकारों के बारे में पर्याप्त जानकारी नहीं पा सका , इसलिए मैंने सोचा कि मैं इसके बारे में यहाँ पूछूंगा।

वर्तमान में, मैं Dictionaryउन सभी उपयोगकर्ताओं को पकड़ने के लिए उपयोग करता हूं जो लगातार कई थ्रेड्स (थ्रेड पूल से, ताकि थ्रेड्स की कोई सटीक मात्रा) तक पहुंच न हो, और इसकी सिंक्रनाइज़ एक्सेस हो।

मुझे हाल ही में पता चला कि .NET 4.0 में थ्रेड-सुरक्षित संग्रह का एक सेट था, और यह बहुत ही मनभावन प्रतीत होता है। मैं सोच रहा था, 'अधिक कुशल और प्रबंधन करने में आसान' विकल्प क्या होगा, क्योंकि मेरे पास Dictionaryसिंक्रनाइज़ एक्सेस के साथ एक सामान्य विकल्प है , या ConcurrentDictionaryजो पहले से ही थ्रेड-सुरक्षित है।

.NET 4.0 का संदर्भ ConcurrentDictionary


जवाबों:


146

एक थ्रेड-सुरक्षित संग्रह बनाम एक गैर-थ्रेडसेफ़-संग्रह पर एक अलग तरीके से देखा जा सकता है।

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

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

अब इस पर विचार करें। एक क्लर्क के साथ स्टोर में, क्या होगा अगर आपको लाइन के सामने का रास्ता मिल जाए, और क्लर्क से पूछें "क्या आपके पास कोई टॉयलेट पेपर है", और वह कहता है "हाँ", और फिर आप "ओके, आई 'जाओ। जब मुझे पता चलेगा कि मुझे आपकी आवश्यकता है, तो आप वापस आ जाएंगे, "तब तक आप पंक्ति में सबसे आगे होंगे, तब तक स्टोर को बेचा जा सकता है। इस परिदृश्य को थ्रेडसेफ़ संग्रह द्वारा रोका नहीं जाता है।

एक थ्रेडसेफ़ संग्रह की गारंटी देता है कि इसकी आंतरिक डेटा संरचनाएं हर समय मान्य हैं, भले ही कई थ्रेड्स से एक्सेस किया गया हो।

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

एक थ्रेडसेफ़ संग्रह हालांकि, गारंटी नहीं देता है कि थ्रेड पर अनुक्रमिक संचालन सभी इसकी आंतरिक डेटा संरचना के समान "स्नैपशॉट" पर काम करते हैं, जिसका अर्थ है कि यदि आपके पास इस तरह का कोड है:

if (tree.Count > 0)
    Debug.WriteLine(tree.First().ToString());

आपको NullReferenceException मिल सकती है क्योंकि Inbetween tree.Countऔर tree.First(), एक अन्य थ्रेड ने ट्री में शेष नोड्स को हटा दिया है, जिसका अर्थ है First()कि वापस आ जाएगा null

इस परिदृश्य के लिए, आपको या तो यह देखना होगा कि क्या प्रश्न में संग्रह के लिए एक सुरक्षित तरीका है जो आप चाहते हैं, शायद आपको ऊपर दिए गए कोड को फिर से लिखना होगा, या आपको लॉक करने की आवश्यकता हो सकती है।


9
हर बार मैंने इस लेख को पढ़ा, भले ही यह सब सच हो, लेकिन किसी तरह हम गलत रास्ते पर जा रहे हैं।
स्कोप_क्रीप सिप

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

89
यह थ्रेड सुरक्षा का एक अच्छा विवरण है, हालाँकि मैं मदद नहीं कर सकता लेकिन ऐसा महसूस करता हूँ कि वास्तव में यह ओपी के प्रश्न को संबोधित नहीं करता था। ओपी ने जो पूछा (और जो मैं बाद में इस सवाल की तलाश में आया था) एक मानक का उपयोग करने Dictionaryऔर अपने आप को लॉक करने से निपटने के बीच का अंतर था। जो ConcurrentDictionaryकि .NET 4+ में बनाया गया है। मैं वास्तव में थोड़ा चकित हूं कि यह स्वीकार किया गया था।
mclark1129

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

2
मुझे लगता है कि @ LasseV.Karlsen जो कहना चाह रही है, वह यह है कि ... थ्रेड सेफ्टी हासिल करना आसान है, लेकिन आपको उस सुरक्षा को संभालने के तरीके में एक स्तर की आपूर्ति करनी चाहिए। अपने आप से यह पूछें, "क्या मैं ऑब्जेक्ट थ्रेड-सुरक्षित रखते हुए एक ही समय में अधिक संचालन कर सकता हूं?" इस उदाहरण पर विचार करें: दो ग्राहक विभिन्न वस्तुओं को वापस करना चाहते हैं। जब आप प्रबंधक के रूप में, अपनी दुकान को फिर से शुरू करने के लिए यात्रा करते हैं, तो क्या आप दोनों वस्तुओं को एक यात्रा में नहीं रोक सकते हैं और फिर भी दोनों ग्राहकों के अनुरोधों को संभाल सकते हैं, या क्या आप किसी ग्राहक द्वारा एक आइटम वापस करने पर हर बार यात्रा करने जा रहे हैं?
अलेक्जेंड्रू

68

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

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

इसलिए उनका उपयोग करें, लेकिन यह मत सोचो कि यह आपको सभी संगामिति मुद्दों की अनदेखी करने के लिए एक मुफ्त पास देता है। आपको अभी भी सावधान रहने की आवश्यकता है।


4
तो मूल रूप से आप कह रहे हैं यदि आप वस्तु का सही उपयोग नहीं करते हैं तो यह काम नहीं करेगा?
चोसपंडियन

3
मूल रूप से आपको आम युक्तियों के बजाय TryAdd का उपयोग करने की आवश्यकता है -> जोड़ें।
चोसपांडियन

3
@ ब्रूनो: नहीं। यदि आपने लॉक नहीं किया है, तो हो सकता है कि दूसरे थ्रेड ने दोनों कॉल के बीच की चाबी निकाल ली हो।
मार्क बायर्स

13
यहाँ पर कर्कश ध्वनि का मतलब नहीं है, लेकिन TryGetValueहमेशा इसका हिस्सा रहा है Dictionaryऔर हमेशा अनुशंसित दृष्टिकोण रहा है। पेश करने वाले महत्वपूर्ण "समवर्ती" तरीके ConcurrentDictionaryहैं AddOrUpdateऔर GetOrAdd। तो, अच्छा जवाब, लेकिन एक बेहतर उदाहरण उठाया जा सकता है।
हारून ने

4
सही - डेटाबेस के विपरीत जिसमें लेन-देन होता है, सबसे समवर्ती-इन-मेमोरी सर्च स्ट्रक्चर अलगाव की अवधारणा को याद करते हैं , वे केवल आंतरिक डेटा संरचना को सही होने की गारंटी देते हैं।
चांग

39

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

मैं कहूंगा, बस समवर्ती का उपयोग करें।


15

मुझे लगता है कि समवर्ती छायाचित्र। GetOrAdd विधि वास्तव में सबसे बहु-थ्रेडेड परिदृश्यों की आवश्यकता है।


1
मैं TryGet का भी उपयोग करूंगा, अन्यथा आप पहले से मौजूद कुंजी को हर बार GetOrAdd के लिए दूसरे पैरामीटर को शुरू करने में प्रयास को बर्बाद करते हैं।
जॉरिटि शपर्स

7
GetOrAdd में अधिभार GetOrAdd (TKey, Func <TKey, TValue>) है , इसलिए दूसरा पैरामीटर केवल शब्दकोष में मौजूद नहीं होने की स्थिति में आलसी-आरंभिक हो सकता है। इसके अलावा TryGetValue का उपयोग डेटा लाने के लिए किया जाता है, संशोधित करने में नहीं। इन विधियों में से प्रत्येक के बाद कॉल के कारण हो सकता है कि दो कॉल के बीच एक और धागा जोड़ा या हटा दिया गया हो।
सग्गनाजगॉन

यह प्रश्न का चिह्नित उत्तर होना चाहिए, भले ही लेसेस का पद उत्कृष्ट हो।
स्पिऑनसियस

13

क्या आपने .net 3.5sp1 के लिए रिएक्टिव एक्सटेंशन्स देखे हैं । जॉन स्कीट के अनुसार, उन्होंने .net3.5 sp1 के लिए समानांतर एक्सटेंशन और समवर्ती डेटा संरचनाओं का एक बंडल वापस कर दिया है।

.Net 4 बीटा 2 के लिए नमूनों का एक सेट है, जो उन्हें समानांतर एक्सटेंशन का उपयोग करने के तरीके पर बहुत अच्छा विवरण देता है।

मैंने अभी पिछले हफ्ते 32 समांतर परिक्षणों का परीक्षण किया है जिसमें I / O प्रदर्शन करने के लिए 32 थ्रेड्स का उपयोग किया गया है। यह विज्ञापन के रूप में काम करने लगता है, जो यह संकेत देता है कि इसमें बहुत अधिक मात्रा में परीक्षण किया गया है।

संपादित करें : .NET 4 समवर्ती और पैटर्न।

Microsoft ने Paralell Programming के पैटर्न नामक एक pdf जारी किया है। इसके वास्तविक रूप से डाउनलोड करने के लायक है क्योंकि यह .Net 4 समवर्ती एक्सटेंशन और बचने के लिए विरोधी पैटर्न का उपयोग करने के लिए वास्तव में अच्छे विवरणों में वर्णित है। यही पर है।


4

मूल रूप से आप नए समवर्ती के साथ जाना चाहते हैं। थ्रेड सुरक्षित प्रोग्राम बनाने के लिए आपको बॉक्स के ठीक बाहर कम कोड लिखना होगा।


अगर मैं समसामयिक स्वच्छता के साथ आगे बढ़ता हूं तो क्या कोई समस्या है?
kbvishnu

1

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

private ConcurrentDictionary<int, Order> _dict;
private Decimal _totalAmount = 0;

while (await enumerator.MoveNextAsync())
{
    Order order = enumerator.Current;
    _dict.TryAdd(order.Id, order);
    _totalAmount += order.Amount;
}

यह कोड थ्रेड सुरक्षित नहीं है। _totalAmountफ़ील्ड को अद्यतन करने वाले कई थ्रेड्स इसे दूषित स्थिति में छोड़ सकते हैं। तो आप इसे एक से बचाने की कोशिश कर सकते हैं lock:

_dict.TryAdd(order.Id, order);
lock (_locker) _totalAmount += order.Amount;

यह कोड "सुरक्षित" है, लेकिन फिर भी थ्रेड सुरक्षित नहीं है। इसमें कोई गारंटी नहीं है कि _totalAmountयह शब्दकोश में प्रविष्टियों के अनुरूप है। यूआई तत्व को अपडेट करने के लिए एक और धागा इन मूल्यों को पढ़ने की कोशिश कर सकता है:

Decimal totalAmount;
lock (_locker) totalAmount = _totalAmount;
UpdateUI(_dict.Count, totalAmount);

totalAmountशब्दकोश में आदेशों की गिनती के अनुरूप नहीं हो सकता है। प्रदर्शित आँकड़े गलत हो सकते हैं। इस बिंदु पर आपको एहसास होगा कि lockडिक्शनरी के अद्यतन को शामिल करने के लिए आपको सुरक्षा का विस्तार करना होगा :

// Thread A
lock (_locker)
{
    _dict.TryAdd(order.Id, order);
    _totalAmount += order.Amount;
}

// Thread B
int ordersCount;
Decimal totalAmount;
lock (_locker)
{
    ordersCount = _dict.Count;
    totalAmount = _totalAmount;
}
UpdateUI(ordersCount, totalAmount);

यह कोड पूरी तरह से सुरक्षित है, लेकिन एक का उपयोग करने के सभी लाभ ConcurrentDictionaryचले गए हैं।

  1. प्रदर्शन सामान्य से अधिक खराब हो गया है Dictionary, क्योंकि आंतरिक लॉकिंग ConcurrentDictionaryअब बेकार और अनावश्यक है।
  2. आपको साझा चर के असुरक्षित उपयोग के लिए अपने सभी कोड की समीक्षा करनी चाहिए।
  3. आप एक अजीब एपीआई का उपयोग कर के साथ फंस रहे हैं ( TryAdd?, AddOrUpdate?)।

इसलिए मेरी सलाह है: Dictionary+ से शुरू करें lock, और बाद में अपग्रेड करने के विकल्प ConcurrentDictionaryको एक प्रदर्शन अनुकूलन के रूप में रखें, अगर यह विकल्प वास्तव में व्यवहार्य है। कई मामलों में यह नहीं होगा।


0

हमने कैश्ड संग्रह के लिए समवर्ती का उपयोग किया है, जो प्रत्येक 1 घंटे में फिर से आबाद हो जाता है और फिर कई क्लाइंट थ्रेड्स द्वारा पढ़ा जाता है, क्या यह उदाहरण थ्रेड के लिए समाधान के समान है ? सवाल।

हमने पाया, कि इसे रीडऑनलाइडर में बदलने से   समग्र प्रदर्शन में सुधार हुआ।


ReadOnlyDictionary एक और IDfox के आसपास सिर्फ आवरण है। हुड के नीचे आप क्या उपयोग करते हैं?
2055 पर Lu55

@ Lu55, हम MS .Net पुस्तकालय का उपयोग कर रहे थे और उपलब्ध / लागू शब्दकोशों में से चुनें। मुझे
एमएसऑन के
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.