2016-10-24 11 views
17

मैं एंटनी विलियम्स द्वारा following लेख को पढ़ने के और जैसा कि मैंने अलावा std::shared_ptr में परमाणु साझा गिनती के लिए std::experimental::atomic_shared_ptr में वास्तविक सूचक साझा वस्तु समझा भी परमाणु है?std :: shared_ptr और std :: experimental :: atomic_shared_ptr के बीच क्या अंतर है?

लेकिन जब मैं lock_free_stack एंटनी की पुस्तक में वर्णित के संदर्भ में गिना संस्करण के बारे में पढ़ा के बारे में C++ Concurrency यह मेरे लिए लगता है कि यह भी std::shared_ptr के लिए एक ही aplies, क्योंकि कार्यों std::atomic_load की तरह, std::atomic_compare_exchnage_weakstd::shared_ptr के उदाहरण के लिए लागू कर रहे हैं।

template <class T> 
class lock_free_stack 
{ 
public: 
    void push(const T& data) 
    { 
    const std::shared_ptr<node> new_node = std::make_shared<node>(data); 
    new_node->next = std::atomic_load(&head_); 
    while (!std::atomic_compare_exchange_weak(&head_, &new_node->next, new_node)); 
    } 

    std::shared_ptr<T> pop() 
    { 
    std::shared_ptr<node> old_head = std::atomic_load(&head_); 
    while(old_head && 
      !std::atomic_compare_exchange_weak(&head_, &old_head, old_head->next)); 
    return old_head ? old_head->data : std::shared_ptr<T>(); 
    } 

private: 
    struct node 
    { 
    std::shared_ptr<T> data; 
    std::shared_ptr<node> next; 

    node(const T& data_) : data(std::make_shared<T>(data_)) {} 
    }; 

private: 
    std::shared_ptr<node> head_; 
}; 

स्मार्ट संकेत के इस दो प्रकार के बीच सटीक अंतर क्या है, और अगर std::shared_ptr उदाहरण में सूचक परमाणु नहीं है, कारण है कि यह ऊपर ताला मुक्त ढेर कार्यान्वयन संभव है?

+0

यह लॉक मुक्त नहीं है। 'Atomic_ *' के विशिष्ट कार्यान्वयन म्यूटेक्स की वैश्विक सरणी का उपयोग करते हैं। –

उत्तर

16

shared_ptr पर परमाणु "चीज़" साझा सूचक नहीं है, लेकिन नियंत्रण ब्लॉक इसे इंगित करता है। जिसका अर्थ यह है कि जब तक आप एकाधिक धागे में shared_ptr को म्यूट नहीं करते हैं, तो आप ठीक हैं। ध्यान दें कि की प्रतिलिपि shared_ptr केवल नियंत्रण ब्लॉक को म्यूट करता है, न कि shared_ptr स्वयं।

std::shared_ptr<int> ptr = std::make_shared<int>(4); 
std::thread threadA([&ptr]{ 
    ptr = std::make_shared<int>(10); 
}); 
std::thread threadB([&ptr]{ 
    ptr = std::make_shared<int>(20); 
});  

यहाँ, हम नियंत्रण ब्लॉक (जो ठीक है परिवर्तनशील होते हैं:

std::shared_ptr<int> ptr = std::make_shared<int>(4); 
for (auto i =0;i<10;i++){ 
    std::thread([ptr]{ auto copy = ptr; }).detach(); //ok, only mutates the control block 
} 

साझा सूचक ही स्वचालित रूप से परिवर्तित, इस तरह के एक से अधिक थ्रेड से अलग मान निर्दिष्ट के रूप में, एक डेटा दौड़, उदाहरण के लिए है) लेकिन साझा पॉइंटर भी, इसे कई धागे से अलग मानों को इंगित करके। यह ठीक नहीं है।

उस समस्या का समाधान shared_ptr को लॉक के साथ लपेटना है, लेकिन यह समाधान कुछ विवाद के तहत इतना स्केलेबल नहीं है, और एक अर्थ में, मानक साझा पॉइंटर की स्वचालित भावना खो देता है।

एक और समाधान आपके द्वारा उद्धृत मानक कार्यों का उपयोग करना है, जैसे std::atomic_compare_exchange_weak। यह साझा पॉइंटर्स को मैन्युअल रूप से सिंक्रनाइज़ करने का काम करता है, जिसे हम पसंद नहीं करते हैं।

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

+0

उत्तर के लिए 10x लेकिन मुझे समझ में नहीं आता कि 'std :: shared_ptr' के उदाहरणों पर' std :: atomic_compare_exchange_weak' के रूप में कार्य सही तरीके से कैसे काम करते हैं, 'std :: shared_ptr' स्वयं परमाणु नहीं है और क्या वे अधिक असुविधाजनक को छोड़कर हैं , 'std :: atomic_shared_ptr' के तरीकों से भी धीमा है? – bobeff

+1

"परमाणु" सीपीयू द्वारा आपूर्ति पाठक-लेखक स्मृति बाड़ के आसपास एक सुविधा wrapper है। "परमाणु" के रूप में एक प्रकार की घोषणा करना कुछ परमाणु बनाने का एक तरीका है। –

+2

@bobeff दोनों libstdC++ और libC++ गैर-परमाणु 'shared_ptr' के शीर्ष पर इन परिचालनों को लागू करने के लिए mutexes (!) की वैश्विक निश्चित-आकार हैश तालिका का उपयोग करें। उन्होंने mutexes में से किसी एक को चुनने के लिए 'shared_ptr' ऑब्जेक्ट का पता है, इसे लॉक करें, फिर गैर-परमाणु संचालन करें। – dyp

5

एक shared_ptr पर std::atomic_load() या std::atomic_compare_exchange_weak() कॉलिंग कार्यात्मक atomic_shared_ptr::load() या atomic_shared_ptr::atomic_compare_exchange_weak() बुला के बराबर है। दोनों के बीच कोई प्रदर्शन अंतर नहीं होना चाहिए। या std::atomic_compare_exchange_weak() पर atomic_shared_ptr पर कॉल करना सिंटैक्टिक रूप से अनावश्यक होगा और प्रदर्शन पेनल्टी नहीं हो सकता है या नहीं।

3

atomic_shared_ptr एक एपीआई परिष्करण है। shared_ptr पहले से ही परमाणु संचालन का समर्थन करता है, लेकिन केवल उपयुक्त atomic non-member functions का उपयोग करते समय। यह त्रुटि-प्रवण है, क्योंकि गैर-परमाणु संचालन उपलब्ध रहते हैं और एक अनचाहे प्रोग्रामर के लिए दुर्घटना से आह्वान करना बहुत आसान होता है। atomic_shared_ptr कम त्रुटि-प्रवण है क्योंकि यह किसी गैर-परमाणु संचालन का खुलासा नहीं करता है।

shared_ptr और atomic_shared_ptr विभिन्न एपीआई का पर्दाफाश करते हैं, लेकिन उन्हें अलग-अलग लागू करने की आवश्यकता नहीं है; shared_ptratomic_shared_ptr द्वारा उजागर किए गए सभी कार्यों का समर्थन करता है। ऐसा कहकर, shared_ptr पर परमाणु संचालन उतना कुशल नहीं हैं जितना वे हो सकते हैं, क्योंकि इसे गैर-परमाणु संचालन का भी समर्थन करना चाहिए। इसलिए प्रदर्शन कारण हैं atomic_shared_ptr अलग-अलग लागू किए जा सकते हैं। यह एक जिम्मेदारी सिद्धांत से संबंधित है। "कई अलग-अलग उद्देश्यों वाली एक इकाई ... अक्सर अपने किसी भी विशिष्ट उद्देश्यों के लिए अपंग इंटरफेस प्रदान करती है क्योंकि कार्यक्षमता के विभिन्न क्षेत्रों में आंशिक ओवरलैप प्रत्येक को लागू करने के लिए आवश्यक दृष्टि को धुंधला करता है।" (Sutter & Alexandrescu 2005, सी ++ मानक कोडिंग)

+0

का उपयोग करके भी बदतर है "उनके आंतरिक को अलग-अलग लागू करने का कोई कारण नहीं है।" हालांकि, है। 'atomic_shared_ptr' इसके साथ एक सिंक्रनाइज़ेशन आदिम ले जा सकता है। यदि आप 'shared_ptr' के साथ ऐसा करते हैं तो आप उन विशाल उपयोगों को निराश कर देंगे जिन्हें परमाणु पहुंच की आवश्यकता नहीं है। –

+0

@ टी.सी. यह एक अच्छा मुद्दा है, मैं अपना जवाब समायोजित करूंगा। – Oktalist

5

N4162(पीडीएफ), परमाणु स्मार्ट संकेत के लिए प्रस्ताव, एक अच्छा व्याख्या है। यहां प्रासंगिक भाग का उद्धरण दिया गया है:

संगति। जहां तक ​​मुझे पता है, [util.smartptr.shared.atomic] फ़ंक्शन मानक में एकमात्र परमाणु संचालन हैं जो atomic प्रकार के माध्यम से उपलब्ध नहीं हैं। और के अलावा shared_ptr के अलावा, हम प्रोग्रामर को सी ++ में परमाणु प्रकार का उपयोग करने के लिए सिखाते हैं, atomic_* सी-शैली फ़ंक्शन नहीं। और यह कुछ हद तक है ...

सुधार। मुक्त फ़ंक्शंस का उपयोग कोड त्रुटि-प्रवण और डिफ़ॉल्ट रूप से रैसी बनाता है। यह कहीं चर घोषणा ही पर एक बार atomic लिखने के लिए बेहतर है और पता है सब तक पहुँचता , परमाणु हो जाएगा बजाय, वस्तु के हर उपयोग पर atomic_* आपरेशन उपयोग करने के लिए याद करने के लिए भी जाहिरा तौर पर-सादे पढ़ता होने के। बाद की शैली त्रुटि-प्रवण है; उदाहरण के लिए, बस लिख खाली स्थान के (जैसे, headatomic_load(&head) के बजाय), ताकि इस शैली में चर के हर इस्तेमाल होता है "यह गलत कर" का मतलब है "डिफ़ॉल्ट रूप से गलत।" आप को भूल जाते हैं में atomic_* कॉल बारे में यहां तक ​​कि एक स्थान, आपका कोड अभी भी किसी भी त्रुटि या चेतावनियों के बिना सफलतापूर्वक संकलित होगा, यह "काम करने के लिए " दिखाई देगा, जिसमें संभवतः अधिकतम परीक्षण शामिल है, लेकिन इसमें अभी भी अनावश्यक व्यवहार के साथ मूक दौड़ होगी जो आम तौर पर हार्ड- विफलताओं को पुन: उत्पन्न करना, अक्सर/आमतौर पर क्षेत्र में, और मैं कुछ मामलों में भी शोषण योग्य भेद्यता की अपेक्षा करता हूं। त्रुटियों के इन कक्षाओं बस चर atomic, घोषित क्योंकि तब यह डिफ़ॉल्ट रूप से सुरक्षित है और कीड़े के एक ही सेट लिखने के द्वारा समाप्त हो जाते स्पष्ट गैर-सफ़ेद कोड की आवश्यकता है (कभी कभी स्पष्ट memory_order_* तर्क, और आमतौर पर reinterpret_cast ing)।

प्रदर्शनatomic_shared_ptr<> अलग प्रकार के रूप में [util.smartptr.shared.atomic] में कार्यों पर एक महत्वपूर्ण दक्षता लाभ दिया है - यह बस atomic<bigstruct> के लिए हमेशा की तरह आंतरिक spinlock के लिए एक अतिरिक्त atomic_flag (या समान) स्टोर कर सकते हैं।इसके विपरीत, मौजूदा स्टैंडअलोन फ़ंक्शंस किसी भी मनमाने ढंग से shared_ptr ऑब्जेक्ट पर प्रयोग करने योग्य होने की आवश्यकता है, भले ही shared_ptr एस का विशाल बहुमत परमाणु रूप से उपयोग नहीं किया जाएगा। यह नि: शुल्क कार्यों को मूल रूप से कम कुशल बनाता है; उदाहरण के लिए, कार्यान्वयन हर shared_ptr आवश्यकता हो सकती है एक आंतरिक spinlock चर के भूमि के ऊपर (बेहतर संगामिति, लेकिन shared_ptr प्रति महत्वपूर्ण ओवरहेड) ले जाने के लिए, या फिर पुस्तकालय shared_ptr के लिए अतिरिक्त जानकारी स्टोर करने के लिए एक lookaside डेटा संरचना को बनाए रखना होगा एस वास्तव में परमाणु रूप से प्रयोग किया जाता है, या ( अभ्यास में सबसे खराब और स्पष्ट रूप से आम है) पुस्तकालय को वैश्विक स्पिनलॉक का उपयोग करना चाहिए।

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