खोज…


टिप्पणियों

एक ही मेमोरी लोकेशन पर पहुंचने की कोशिश करने वाले विभिन्न थ्रेड्स डेटा रेस में भाग लेते हैं यदि कम से कम एक ऑपरेशन एक संशोधन है (जिसे ऑपरेशन के रूप में भी जाना जाता है)। ये डेटा दौड़ अपरिभाषित व्यवहार का कारण बनती हैं । इनसे बचने के लिए इन धागों को समवर्ती रूप से इस तरह के परस्पर विरोधी कार्यों को रोकने की आवश्यकता है।

सिंक्रोनाइज़ेशन प्रिमिटिव (म्यूटेक्स, क्रिटिकल सेक्शन और लाइक) इस तरह के एक्सेस को गार्ड कर सकते हैं। C ++ 11 में शुरू किया गया मेमोरी मॉडल मल्टी-थ्रेडेड वातावरण में मेमोरी तक पहुंच को सिंक्रनाइज़ करने के लिए दो नए पोर्टेबल तरीकों को परिभाषित करता है: परमाणु संचालन और बाड़।

परमाणु संचालन

परमाणु भार और परमाणु स्टोर संचालन के उपयोग द्वारा दी गई स्मृति स्थान को पढ़ना और लिखना अब संभव है। सुविधा के लिए इन्हें std::atomic<t> टेम्पलेट वर्ग में लपेटा जाता है। यह वर्ग टाइप t मान को लपेटता है लेकिन इस बार ऑब्जेक्ट पर लोड और स्टोर परमाणु हैं।

सभी प्रकार के लिए टेम्पलेट उपलब्ध नहीं है। कौन से प्रकार उपलब्ध हैं, कार्यान्वयन विशिष्ट है, लेकिन इसमें आमतौर पर उपलब्ध (या सभी) अभिन्न प्रकार के साथ-साथ सूचक प्रकार भी शामिल हैं। ताकि std::atomic<unsigned> और std::atomic<std::vector<foo> *> उपलब्ध होना चाहिए, जबकि std::atomic<std::pair<bool,char>> most शायद wont हो।

परमाणु संचालन में निम्नलिखित गुण होते हैं:

  • सभी परमाणु परिचालनों को अपरिभाषित व्यवहार के बिना कई थ्रेड्स से समवर्ती रूप से निष्पादित किया जा सकता है।
  • एक परमाणु भार या तो प्रारंभिक मूल्य को देखेगा जो परमाणु वस्तु के साथ निर्मित किया गया था, या कुछ परमाणु स्टोर ऑपरेशन के माध्यम से इसे लिखा गया मूल्य।
  • एक ही परमाणु वस्तु के परमाणु भंडार को सभी धागों में एक समान रखने का आदेश दिया गया है। यदि किसी थ्रेड ने पहले से ही कुछ परमाणु स्टोर ऑपरेशन के मूल्य को देख लिया है, तो बाद में परमाणु भार संचालन या तो एक ही मूल्य, या बाद के परमाणु स्टोर ऑपरेशन द्वारा संग्रहीत मूल्य देखेंगे।
  • परमाणु पढ़ने-संशोधित-लिखने के संचालन परमाणु भार और परमाणु स्टोर को बीच में अन्य परमाणु स्टोर के बिना होने की अनुमति देते हैं। उदाहरण के लिए, एक व्यक्ति कई धागों से एक काउंटर बढ़ा सकता है, और धागे के बीच विवाद की परवाह किए बिना कोई वेतन वृद्धि नहीं होगी।
  • परमाणु संचालन को एक वैकल्पिक std::memory_order प्राप्त होता है std::memory_order पैरामीटर जो परिभाषित करता है कि अन्य मेमोरी स्थानों के बारे में ऑपरेशन में कौन से अतिरिक्त गुण हैं।
std :: memory_order अर्थ
std::memory_order_relaxed कोई अतिरिक्त प्रतिबंध नहीं
std::memory_order_releasestd::memory_order_acquire अगर load-acquire द्वारा संग्रहीत मूल्य देखता store-release से पहले अनुक्रम भंडार store-release लोड अधिग्रहण के बाद अनुक्रम लोड होने से पहले हो
std::memory_order_consume जैसे कि memory_order_acquire लेकिन केवल निर्भर भार के लिए
std::memory_order_acq_rel load-acquire और store-release को जोड़ती है
std::memory_order_seq_cst क्रमिक संगति

ये मेमोरी ऑर्डर टैग तीन अलग-अलग मेमोरी ऑर्डरिंग डिसिप्लिन की अनुमति देते हैं: क्रमिक स्थिरता , आराम , और अपने सहोदर रिलीज-उपभोग के साथ रिलीज-अधिग्रहण

अनुक्रमिक संगति

यदि कोई मेमोरी ऑर्डर परमाणु ऑपरेशन के लिए निर्दिष्ट नहीं है, तो क्रम अनुक्रमिक स्थिरता के लिए चूक जाता है । इस मोड को std::memory_order_seq_cst साथ ऑपरेशन को टैग करके स्पष्ट रूप से चुना जा सकता है।

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

आराम से आदेश देना

अनुक्रमिक स्थिरता के विपरीत, आराम करने वाला मेमोरी ऑर्डर है। इसे std::memory_order_relaxed टैग के साथ चुना जाता है। आराम से किए गए परमाणु ऑपरेशन अन्य मेमोरी ऑपरेशन पर कोई प्रतिबंध नहीं लगाएंगे। एकमात्र प्रभाव जो बना हुआ है, वह यह है कि ऑपरेशन स्वयं अभी भी परमाणु है।

रिलीज-एक्वायर करना आदेश

एक परमाणु स्टोर ऑपरेशन को std::memory_order_release साथ टैग किया जा सकता है और एक परमाणु भार ऑपरेशन को std::memory_order_acquire साथ टैग किया जा सकता है। पहले ऑपरेशन को (परमाणु) स्टोर-रिलीज़ कहा जाता है जबकि दूसरे को (परमाणु) लोड-अधिग्रहण कहा जाता है।

जब लोड-अधिग्रहण एक स्टोर-रिलीज़ द्वारा लिखे गए मूल्य को देखता है, तो निम्न होता है: स्टोर-रिलीज़ से पहले अनुक्रमित सभी स्टोर संचालन लोड-अधिग्रहण के बाद अनुक्रमित लोड ऑपरेशन (जो पहले होता है ) के लिए दृश्यमान हो जाते हैं।

परमाणु पठन-संशोधित-लेखन कार्य भी संचयी टैग std::memory_order_acq_rel प्राप्त कर सकते हैं। यह ऑपरेशन के परमाणु लोड हिस्से को परमाणु लोड-अधिग्रहण करता है जबकि परमाणु स्टोर का हिस्सा परमाणु स्टोर-रिलीज़ हो जाता है।

संकलक को परमाणु स्टोर-रिलीज़ ऑपरेशन के बाद स्टोर संचालन को स्थानांतरित करने की अनुमति नहीं है। इसे परमाणु लोड-अधिग्रहण (या लोड-खपत ) से पहले लोड संचालन को स्थानांतरित करने की अनुमति नहीं है।

यह भी ध्यान दें कि परमाणु भार-विमोचन या परमाणु भंडार-अधिग्रहण नहीं है । इस तरह के ऑपरेशन बनाने का प्रयास उन्हें सुकून देने वाला ऑपरेशन बनाता है।

रिलीज-खपत आदेश

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

बाड़

धागे के बीच स्मृति संचालन को भी आदेश दिया जा सकता है। एक बाड़ या तो एक रिलीज बाड़ है या बाड़ का अधिग्रहण करती है।

यदि एक रिलीज बाड़ एक अधिग्रहण बाड़ से पहले होती है, तो रिलीज बाड़ के बाद अनुक्रमित लोड करने के लिए दृश्यमान होने से पहले अनुक्रमित भंडार संग्रहीत होते हैं। यह गारंटी देने के लिए कि रिलीज बाड़, अधिग्रहण बाड़ से पहले होती है, आराम से परमाणु संचालन सहित अन्य सिंक्रनाइज़ेशन प्राइमेटिव का उपयोग कर सकती है।

मेमोरी मॉडल की आवश्यकता

int x, y;
bool ready = false;

void init()
{
  x = 2;
  y = 3;
  ready = true;
}
void use()
{
  if (ready)
    std::cout << x + y;
}

एक थ्रेड init() फ़ंक्शन को कॉल करता है, जबकि दूसरा थ्रेड (या सिग्नल हैंडलर) use() फ़ंक्शन को कॉल करता है। एक उम्मीद कर सकता है कि use() फ़ंक्शन या तो 5 प्रिंट करेगा या कुछ भी नहीं करेगा। यह हमेशा कई कारणों से नहीं हो सकता है:

  • सीपीयू उन राइट्स को फिर से लिख सकता है जो इनिट में होते हैं init() ताकि कोड जो वास्तव में निष्पादित होता है वह इस तरह दिखाई दे:

    void init()
    {
      ready = true;
      x = 2;
      y = 3;
    }
    
  • CPU use() में होने वाली रीड्स को फिर से नियंत्रित कर सकता है use() ताकि वास्तव में निष्पादित कोड बन जाए:

    void use()
    {
      int local_x = x;
      int local_y = y;
      if (ready)
        std::cout << local_x + local_y;
    }
    
  • एक अनुकूलन C ++ कंपाइलर प्रोग्राम को उसी तरह से फिर से व्यवस्थित करने का निर्णय ले सकता है।

इस तरह के पुनरावृत्ति एकल थ्रेड में चल रहे प्रोग्राम के व्यवहार को बदल नहीं सकते क्योंकि एक थ्रैट कॉल को init() और use() नहीं कर सकता है। एक बहु-थ्रेडेड सेटिंग में दूसरी ओर एक धागा दूसरे धागे द्वारा किए गए लेखन का हिस्सा हो सकता है जहां ऐसा हो सकता है कि use() ready==true हो सकता है ready==true x या y या दोनों में ready==true और कचरा।

सी ++ मेमोरी मॉडल प्रोग्रामर को यह निर्दिष्ट करने की अनुमति देता है कि कौन से पुन: व्यवस्थित संचालन की अनुमति है और जो नहीं हैं, ताकि बहु-थ्रेडेड प्रोग्राम भी अपेक्षित रूप से व्यवहार करने में सक्षम हो। उपरोक्त उदाहरण को इस तरह से थ्रेड-सुरक्षित तरीके से फिर से लिखा जा सकता है:

int x, y;
std::atomic<bool> ready{false};

void init()
{
  x = 2;
  y = 3;
  ready.store(true, std::memory_order_release);
}
void use()
{
  if (ready.load(std::memory_order_acquire))
    std::cout << x + y;
}

यहाँ init() परमाणु स्टोर-रिलीज़ ऑपरेशन करता है। यह न केवल ready मूल्य को true ready , बल्कि संकलक को यह भी बताता है कि वह इस ऑपरेशन को लिखने से पहले स्थानांतरित नहीं कर सकता है जो इसके पहले अनुक्रमित हैं।

use() फ़ंक्शन एक परमाणु लोड-अधिग्रहण ऑपरेशन करता है। यह के वर्तमान मूल्य पढ़ता ready है और यह भी पढ़ा आपरेशन के बाद यह परमाणु भार: प्राप्त करने से पहले होने की अनुक्रम कर रहे हैं रखने से संकलक रोकता है।

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

क्योंकि परमाणु स्टोर-रिलीज़ परमाणु लोड-अधिग्रहण के समान स्मृति स्थान पर है, मेमोरी मॉडल यह निर्धारित करता है कि यदि लोड-अधिग्रहण ऑपरेशन स्टोर-रिलीज़ ऑपरेशन द्वारा लिखे गए मूल्य को देखता है, तो सभी init() द्वारा लिखित लिखते हैं। उस स्टोर-रिलीज़ से पहले का थ्रेड उस लोड use() अधिग्रहण) के लोड के बाद उस use() लोड को दिखाई देगा। यदि use() ready==true देखता ready==true , तो यह x==2 और y==3 देखने की गारंटी है।

ध्यान दें कि कंपाइलर और सीपीयू को अभी भी x लिखने से पहले y को लिखने की अनुमति है, और इसी तरह इन चर use() से पढ़ता है किसी भी क्रम में हो सकता है।

बाड़ का उदाहरण

ऊपर दिए गए उदाहरण को बाड़ और आराम से संचालित परमाणु कार्यों के साथ भी लागू किया जा सकता है:

int x, y;
std::atomic<bool> ready{false};

void init()
{
  x = 2;
  y = 3;
  atomic_thread_fence(std::memory_order_release);
  ready.store(true, std::memory_order_relaxed);
}
void use()
{
  if (ready.load(std::memory_order_relaxed))
  {
    atomic_thread_fence(std::memory_order_acquire);
    std::cout << x + y;
  }
}

यदि परमाणु भार ऑपरेशन परमाणु स्टोर द्वारा लिखे गए मूल्य को देखता है तो स्टोर लोड से पहले होता है, और इसलिए बाड़ करते हैं: रिलीज बाड़ से पहले होता है अधिग्रहण बाड़ x और y को लिखता है कि रिलीज बाड़ बनने से पहले दिखाई देता है std::cout स्टेटमेंट जो कि प्राप्त बाड़ का अनुसरण करता है।

एक बाड़ फायदेमंद हो सकती है यदि यह अधिग्रहण, रिलीज़ या अन्य सिंक्रनाइज़ेशन संचालन की समग्र संख्या को कम कर सकती है। उदाहरण के लिए:

void block_and_use()
{
  while (!ready.load(std::memory_order_relaxed))
    ;
  atomic_thread_fence(std::memory_order_acquire);
  std::cout << x + y;
}

block_and_use() फ़ंक्शन तब तक घूमता है जब तक कि ready ध्वज को आराम से परमाणु भार की मदद से सेट नहीं किया जाता है। तब एक एकल अधिग्रहित बाड़ का उपयोग आवश्यक मेमोरी ऑर्डर देने के लिए किया जाता है।



Modified text is an extract of the original Stack Overflow Documentation
के तहत लाइसेंस प्राप्त है CC BY-SA 3.0
से संबद्ध नहीं है Stack Overflow