सूची :: रिक्त () बहु-थ्रेडेड व्यवहार?


9

मेरे पास एक सूची है जो मुझे तत्वों को हथियाने के लिए अलग-अलग धागे चाहिए। खाली होने पर सूची की रक्षा करने वाले म्यूटेक्स को लॉक करने से बचने के लिए, मैं empty()लॉक करने से पहले जांच करता हूं ।

यदि कॉल list::empty()100% सही नहीं है तो ठीक है । मैं केवल समवर्ती list::push()और list::pop()कॉल को क्रैश या बाधित करने से बचना चाहता हूं ।

क्या मैं वीसी ++ और गुन्नू जीसीसी को संभालने के लिए सुरक्षित हूं, केवल कभी-कभी empty()गलत हो जाएगा और इससे बुरा कुछ नहीं होगा?

if(list.empty() == false){ // unprotected by mutex, okay if incorrect sometimes
    mutex.lock();
    if(list.empty() == false){ // check again while locked to be certain
         element = list.back();
         list.pop_back();
    }
    mutex.unlock();
}

1
नहीं, आप यह नहीं मान सकते। आप कुलपति की तरह एक समवर्ती कंटेनर इस्तेमाल कर सकते हैं concurrent_queue
पानागिओटिस Kanavos

2
@Fureeish यह एक उत्तर होना चाहिए। मैं जोड़ूंगा कि std::list::sizeनिरंतर समय की जटिलता की गारंटी है, जिसका मूल अर्थ है कि आकार (नोड्स की संख्या) को एक अलग चर में संग्रहीत करने की आवश्यकता है; चलो इसे बुलाओ size_std::list::emptyतब संभावना है कि कुछ के रूप में वापस आता है size_ == 0, और समवर्ती पढ़ने और लिखने के size_कारण डेटा की दौड़ होगी, इसलिए, यूबी।
डैनियल लैंगर

@DanielLangr "निरंतर समय" कैसे मापा जाता है? क्या यह एकल फ़ंक्शन कॉल या संपूर्ण कार्यक्रम पर है?
जिज्ञासु

1
@ क्यूरियस गुय: डैनियललंगर ने आपके प्रश्न का उत्तर "सूची नोड्स की संख्या से स्वतंत्र" दिया, जो कि ओ (1) की सटीक परिभाषा है जिसका अर्थ है कि प्रत्येक कॉल को कुछ निरंतर समय से कम समय में निष्पादित किया जाता है, तत्वों की संख्या की परवाह किए बिना। en.wikipedia.org/wiki/Big_O_notation#Orders_of_common_functions अन्य विकल्प (C ++ 11 तक) रैखिक = O (n) होगा, जिसका अर्थ है कि तत्वों (लिंक की गई सूची) को गिनना होगा, जो कि इसके लिए और भी बुरा होगा। संक्षिप्त नाम (काउंटर पर गैर-परमाणु पढ़ने / लिखने की तुलना में अधिक स्पष्ट डेटा-दौड़)।
फ़िदा

1
@ क्यूरियस गुय: डीवी के साथ अपना खुद का उदाहरण लेते हुए, समय की जटिलता एक ही गणित है - सीमा। इन सभी चीज़ों को या तो पुनरावर्ती रूप से परिभाषित किया गया है, या "वहाँ मौजूद है जैसे कि C मौजूद है (f) (N) <C हर N के लिए" - यही O (1) की परिभाषा है (दिए गए / हर HW के लिए निरंतर C मौजूद है) यह कि किसी भी इनपुट पर एल-सी से कम समय में अहंकार समाप्त होता है)। परिशोधित साधन औसतन , जिसका मतलब है कि कुछ आदानों लंबी प्रक्रिया के लिए (उदाहरण के लिए फिर से हैश / पुनः alloc आवश्यक) लग सकता है, लेकिन यह अभी भी लगातार औसतन (सभी संभव आदानों कल्पना करते हुए) है।
फ़िदा

जवाबों:


10

यदि कॉल list::empty()100% सही नहीं है तो ठीक है ।

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

यदि आप अपनी पवित्रता को महत्व देते हैं, तो आप प्रदर्शन हिट करेंगे और जाँच से पहले म्यूटेक्स को लॉक करेंगे। उस ने कहा, सूची भी आपके लिए सही कंटेनर नहीं हो सकती है। यदि आप हमें बता सकते हैं कि आप इसके साथ क्या कर रहे हैं, तो हम बेहतर कंटेनर का सुझाव दे सकते हैं।


व्यक्तिगत दृष्टिकोण, कॉल list::empty()एक पढ़ने की क्रिया है जिसका कोई लेना-देना नहीं हैrace-condition
Khánh Nguyễn

3
@ Ng @cKhánhNguyọn यदि वे सूची में तत्वों को जोड़ रहे हैं तो यह निश्चित रूप से डेटा रेस का कारण बनता है क्योंकि आप एक ही समय में आकार लिख रहे हैं और पढ़ रहे हैं।
नाथनऑलिवर

6
@ Ng @cKhánhNguyọn यह गलत है। एक दौड़ की स्थिति read-writeया है write-write। यदि आप मेरे साथ विश्वास नहीं करते हैं, तो डेटा दौड़ पर मानक अनुभाग पढ़ें
NathanOliver

1
@ Ng @cKhánhNguyọn: क्योंकि न तो लिखने और न ही पढ़ने के लिए परमाणु होने की गारंटी है, इसलिए इसे एक साथ निष्पादित किया जा सकता है, इसलिए पढ़ने से कुछ पूरी तरह से गलत हो सकता है (जिसे फटा हुआ पढ़ा जाता है)। थोड़ा एंडियन 8-बिट एमसीयू में 0x00FF को 0x0100 में बदलने की कल्पना करें, 0xFF को 0x00 से कम करके लिखना शुरू करें और अब रीड बिल्कुल शून्य हो जाता है, दोनों बाइट्स पढ़ना (थ्रेड को धीमा या निलंबित करना) लिखना, उच्च बाइट को 0x01 पर अपडेट करके जारी रहता है। लेकिन रीडिंग थ्रेड को पहले ही गलत मान मिला (न तो 0x00FF, न ही 0x0100 लेकिन अप्रत्याशित 0x0000)।
फ़िरदा

1
@ Ng @cKhánhNguyọn यह कुछ आर्किटेक्चर पर हो सकता है लेकिन C ++ वर्चुअल मशीन ऐसी कोई गारंटी नहीं देती है। यहां तक ​​कि अगर आपके हार्डवेयर ने किया था, तो यह कंपाइलर के लिए कोड को इस तरह से ऑप्टिमाइज़ करने के लिए कानूनी होगा कि जब तक थ्रेड सिंक्रोनाइज़ेशन न हो, तब तक आपको कोई बदलाव नहीं दिखाई देगा, यह मान सकते हैं कि यह केवल एक ही थ्रेड चल रहा है और उसी के अनुसार ऑप्टिमाइज़ होता है।
नाथनऑलिवर

6

एक पढ़ा और लिखा हुआ (सबसे शायद sizeसदस्य के लिए std::list, अगर हम मानते हैं कि यह उस तरह का नाम है) जो एक दूसरे के लिए अभिकर्मक में सिंक्रनाइज़ नहीं हैं । कल्पना करें कि एक धागा कॉल empty()(आपके बाहरी में if()) जबकि दूसरा धागा आंतरिक में प्रवेश किया if()और निष्पादित करता है pop_back()। आप तब एक चर पढ़ रहे हैं, जो संभवतः, संशोधित किया जा रहा है। यह अपरिभाषित व्यवहार है।


2

चीजों के गलत होने के उदाहरण के रूप में:

एक पर्याप्त स्मार्ट कंपाइलर देख सकता है कि mutex.lock()संभवतः list.empty()रिटर्न वैल्यू को बदल नहीं सकता है और इस प्रकार आंतरिक ifजांच को पूरी तरह से छोड़ सकता है , अंततः pop_backएक सूची में अग्रणी हो सकता है जिसमें पहले के बाद इसका अंतिम तत्व हटा दिया गया था if

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

यह कई अनुकूलन (या हार्डवेयर व्यवहार) में से एक है जो आपके कोड को तोड़ सकता है।


वर्तमान संकलक भी अनुकूलन नहीं करना चाहते हैं a.load()+a.load()...
curiousguy

1
@curiousguy यह कैसे अनुकूलित होगा? आप वहां पूर्ण अनुक्रमिक संगति का अनुरोध करते हैं, इसलिए आपको वह मिल जाएगा ...
मैक्स लैंगहॉफ़

@MaxLanghof आपको नहीं लगता कि अनुकूलन a.load()*2स्पष्ट है? यहां तक कि नहीं a.load(rel)+b.load(rel)-a.load(rel)वैसे भी अनुकूलित है। कुछ भी नहीं है। आप अधिक अनुकूलित होने के लिए ताले की उम्मीद क्यों करते हैं?
जिज्ञासु

@curiousguy क्योंकि गैर-परमाणु अभिगमन की स्मृति आदेश (यहां ताला से पहले और बाद में) और परमाणु पूरी तरह से अलग हैं? मुझे उम्मीद नहीं है कि लॉक को "अधिक" अनुकूलित किया जाएगा, मैं उम्मीद करता हूं कि असमान सिंक्रोनाइज़ किए गए एक्सेस को क्रमिक रूप से लगातार एक्सेस से अधिक ऑप्टिमाइज़ किया जाए। ताला की उपस्थिति मेरी बात के लिए अप्रासंगिक है। और नहीं, संकलक को अनुकूलित a.load() + a.load()करने की अनुमति नहीं है 2 * a.load()। यदि आप अधिक जानना चाहते हैं, तो इस बारे में प्रश्न पूछने के लिए स्वतंत्र महसूस करें।
मैक्स लैंगहॉफ़

@MaxLanghof मुझे नहीं पता कि आप क्या कहना चाह रहे हैं। ताले अनिवार्य रूप से क्रमिक रूप से सुसंगत हैं। कार्यान्वयन कुछ थ्रेडिंग प्रिमिटिव (ताले) पर अनुकूलन करने की कोशिश क्यों करेगा और कुछ अन्य (एटॉमिक्स) नहीं? क्या आप उम्मीद करते हैं कि गैर-एटमिक्स एक्सेस एटमिक्स के उपयोग के आसपास अनुकूलित हो सकते हैं?
जिज्ञासु
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.