C ++ 11 में StoreLoad बाधा कैसे प्राप्त करें?


13

मैं पोर्टेबल कोड (इंटेल, एआरएम, पावरपीसी ...) लिखना चाहता हूं जो एक क्लासिक समस्या का एक प्रकार हल करता है:

Initially: X=Y=0

Thread A:
  X=1
  if(!Y){ do something }
Thread B:
  Y=1
  if(!X){ do something }

जिसमें लक्ष्य एक ऐसी स्थिति से बचने का है जिसमें दोनों धागे कर रहे हैंsomething । (यह ठीक है अगर कोई बात नहीं चलती है, यह एक रन-बिल्कुल-एक बार तंत्र नहीं है।) कृपया मुझे ठीक करें यदि आपको नीचे मेरे तर्क में कुछ खामियां दिखाई देती हैं।

मुझे पता है, कि मैं memory_order_seq_cstपरमाणु के साथ लक्ष्य प्राप्त कर सकता हूं storeऔर loadनिम्नानुसार है:

std::atomic<int> x{0},y{0};
void thread_a(){
  x.store(1);
  if(!y.load()) foo();
}
void thread_b(){
  y.store(1);
  if(!x.load()) bar();
}

जो लक्ष्य को प्राप्त करता है, क्योंकि
{x.store(1), y.store(1), y.load(), x.load()}घटनाओं पर कुछ एकल कुल आदेश होना चाहिए, जिसे प्रोग्राम ऑर्डर "किनारों" से सहमत होना चाहिए:

  • x.store(1) "टू में पहले है" y.load()
  • y.store(1) "टू में पहले है" x.load()

और अगर foo()बुलाया गया था, तो हमारे पास अतिरिक्त बढ़त है:

  • y.load() "मूल्य से पहले पढ़ता है" y.store(1)

और अगर bar()बुलाया गया था, तो हमारे पास अतिरिक्त बढ़त है:

  • x.load() "मूल्य से पहले पढ़ता है" x.store(1)

और इन सभी किनारों को मिलाकर एक चक्र बनेगा:

x.store(1)"टू में पहले है" y.load()" पहले पढ़ता है मूल्य" से पहले y.store(1)"में है" x.load()"पहले पढ़ता है मूल्य"x.store(true)

जो इस तथ्य का उल्लंघन करता है कि आदेशों का कोई चक्र नहीं है।

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


अब, मेरी वास्तविक समस्या थोड़ी अधिक जटिल है, क्योंकि "X" पर मेरा कोई नियंत्रण नहीं है - यह कुछ मैक्रोज़, टेम्प्लेट आदि के पीछे छिपा हुआ है और इससे कमज़ोर हो सकता है। seq_cst

मुझे यह भी नहीं पता है कि "X" एक एकल चर है, या कुछ अन्य अवधारणा (उदाहरण के लिए एक हल्के वजन वाला सेमाफोर या म्यूटेक्स)। मुझे पता है कि मेरे पास दो मैक्रोज़ हैं set()और check()ऐसे " check()रिटर्न true" के बाद "एक और धागा" कहा जाता है set()। (यह है भी है कि जाना जाता है setऔर checkधागे की सुरक्षित हैं और डेटा-दौड़ यूबी नहीं बना सकते।)

तो वैचारिक रूप set()से कुछ हद तक "एक्स = 1" check()जैसा है और "एक्स" जैसा है, लेकिन मेरे पास एटोमिक्स तक कोई सीधी पहुंच नहीं है, यदि कोई हो।

void thread_a(){
  set();
  if(!y.load()) foo();
}
void thread_b(){
  y.store(1);
  if(!check()) bar();
}

मुझे चिंता है, कि set()आंतरिक रूप से लागू किया जा सकता है x.store(1,std::memory_order_release)और / या check()हो सकता है x.load(std::memory_order_acquire)। या काल्पनिक रूप से std::mutexकि एक धागा अनलॉक हो रहा है और दूसरा try_lockआईएनजी है; आईएसओ मानक std::mutexमें केवल अधिग्रहण और रिलीज ऑर्डर देने की गारंटी है, seq_cst की नहीं।

यदि यह मामला है, तो check()अगर शरीर पहले "फिर से व्यवस्थित" हो सकता है y.store(true)( एलेक्स का जवाब देखें जहां वे प्रदर्शित करते हैं कि यह पावरपीसी पर होता है )।
यह वास्तव में बुरा होगा, क्योंकि अब घटनाओं का यह क्रम संभव है:

  • thread_b()पहले x( 0) के पुराने मूल्य को लोड करता है
  • thread_a() सहित सब कुछ निष्पादित करता है foo()
  • thread_b() सहित सब कुछ निष्पादित करता है bar()

तो, दोनों foo()और bar()बुला लिया गया है, जो मैं से बचने के लिए किया था। इसे रोकने के लिए मेरे पास क्या विकल्प हैं?


विकल्प ए

स्टोर-लोड बाधा को मजबूर करने की कोशिश करें। यह, व्यवहार में, द्वारा प्राप्त किया जा सकता है std::atomic_thread_fence(std::memory_order_seq_cst);- जैसा कि एलेक्स द्वारा एक अलग उत्तर में समझाया गया है सभी परीक्षित संकलक एक पूर्ण बाड़ उत्सर्जित करते हैं:

  • x86_64: MFENCE
  • PowerPC: hwsync
  • इतानुम: mf
  • ARMv7 / ARMv8: dmb ish
  • MIPS64: सिंक

इस दृष्टिकोण के साथ समस्या यह है, कि मुझे C ++ नियमों में कोई गारंटी नहीं मिली, कि std::atomic_thread_fence(std::memory_order_seq_cst)पूर्ण मेमोरी बैरियर में अनुवाद करना होगा। दरअसल, atomic_thread_fenceC ++ में s की अवधारणा स्मृति अवरोधों की असेंबली अवधारणा की तुलना में एक अलग स्तर पर होती है और "परमाणु ऑपरेशन किस चीज़ के साथ सिंक्रनाइज़ होता है" जैसे सामानों के साथ अधिक व्यवहार करता है। क्या कोई सैद्धांतिक प्रमाण है कि नीचे कार्यान्वयन लक्ष्य को प्राप्त करता है?

void thread_a(){
  set();
  std::atomic_thread_fence(std::memory_order_seq_cst)
  if(!y.load()) foo();
}
void thread_b(){
  y.store(true);
  std::atomic_thread_fence(std::memory_order_seq_cst)
  if(!check()) bar();
}

विकल्प बी

नियंत्रण का उपयोग करें हमारे पास वाई पर तुल्यकालन प्राप्त करने के लिए, Y- पर पढ़ने-लिखने-लिखने memory_order_acq_rel संचालन का उपयोग करके:

void thread_a(){
  set();
  if(!y.fetch_add(0,std::memory_order_acq_rel)) foo();
}
void thread_b(){
  y.exchange(1,std::memory_order_acq_rel);
  if(!check()) bar();
}

यहाँ विचार यह है कि एकल परमाणु ( y) तक पहुँच एक एकल क्रम होनी चाहिए जिस पर सभी पर्यवेक्षक सहमत हों, इसलिएfetch_add पहले exchangeया इसके विपरीत है।

यदि fetch_addपहले है, exchangeतो "रिलीज" भाग के fetch_add"अधिग्रहण" भाग के साथ सिंक्रनाइज़ करता है exchangeऔर इस प्रकार सभी साइड इफेक्ट्स set()कोडिंग के लिए दिखाई देते हैंcheck() , इसलिए bar()इसे नहीं बुलाया जाएगा।

अन्यथा, exchangeपहले है fetch_add, फिर fetch_addदेखेंगे 1और कॉल नहीं करेंगे foo()। तो, दोनों को कॉल करना असंभव है foo()औरbar() । क्या यह तर्क सही है?


विकल्प सी

"किनारों" को शुरू करने के लिए डमी परमाणु का उपयोग करें, जो आपदा को रोकते हैं। निम्नलिखित दृष्टिकोण पर विचार करें:

void thread_a(){
  std::atomic<int> dummy1{};
  set();
  dummy1.store(13);
  if(!y.load()) foo();
}
void thread_b(){
  std::atomic<int> dummy2{};
  y.store(1);
  dummy2.load();
  if(!check()) bar();
}

अगर आपको लगता है कि यहाँ समस्या है atomic स्थानीय है, तो उन्हें वैश्विक दायरे में ले जाने की कल्पना करें, निम्नलिखित कारणों से यह मेरे लिए मायने नहीं रखता है, और मैंने जानबूझकर कोड को इस तरह से उजागर करने के लिए लिखा है कि यह कितना हास्यास्पद है और डमी 2 पूरी तरह से अलग हैं।

पृथ्वी पर यह क्यों काम कर सकता है? वैसे, {dummy1.store(13), y.load(), y.store(1), dummy2.load()}कार्यक्रम के "किनारों" के अनुरूप होने के लिए कुछ एकल कुल क्रम होना चाहिए :

  • dummy1.store(13) "टू में पहले है" y.load()
  • y.store(1) "टू में पहले है" dummy2.load()

(एक seq_cst स्टोर + लोड उम्मीद है कि स्टोरलॉड सहित पूर्ण मेमोरी बैरियर के C ++ के बराबर है, जैसे कि वे वास्तविक ISAs पर भी AArch64 सहित अलग-अलग हैं जहां कोई अलग बाधा निर्देश की आवश्यकता नहीं है।)

अब, हमारे पास विचार करने के लिए दो मामले हैं: या तो y.store(1)पहले हैy.load() या बाद में कुल आदेश है।

अगर y.store(1)पहले है y.load()तो foo()नहीं बुलाया जाएगा और हम सुरक्षित हैं।

यदि y.load()पहले है y.store(1), तो इसे दो किनारों के साथ जोड़कर हमारे पास पहले से ही कार्यक्रम क्रम में है, हम इसे घटाते हैं:

  • dummy1.store(13) "टू में पहले है" dummy2.load()

अब, dummy1.store(13)एक रिलीज ऑपरेशन है, जो के प्रभाव को जारी करता है set(), और dummy2.load()एक अधिग्रहण ऑपरेशन है, इसलिए check()इसके प्रभावों को देखना चाहिए set()और इस तरह bar()नहीं बुलाया जाएगा और हम सुरक्षित हैं।

क्या यह सोचना सही है कि check()इसके परिणाम देखने को मिलेंगे set()? क्या मैं विभिन्न प्रकारों के "किनारों" ("प्रोग्राम ऑर्डर" उर्फ ​​अनुक्रम से पहले, "कुल ऑर्डर", "रिलीज से पहले", "अधिग्रहण के बाद") को जोड़ सकता हूं? मुझे इस बारे में गंभीर संदेह है: सी ++ के नियमों के बारे में बात करने लगते हैं "सिंक्रनाइज़-इन" स्टोर और लोड के बीच एक ही स्थान पर संबंध - यहां ऐसी कोई स्थिति नहीं है।

ध्यान दें कि हम केवल इस मामले में जहां के बारे में चिंतित रहे dumm1.storeहै जाना जाता है (अन्य तर्क के माध्यम से) से पहले होने की dummy2.loadseq_cst कुल आदेश में। इसलिए यदि वे एक ही चर पर पहुंच रहे थे, तो लोड ने संग्रहीत मूल्य को देखा होगा और इसके साथ सिंक्रनाइज़ किया गया था।

(मेमोरी-बैरियर / रीऑर्डरिंग कार्यान्वयन के लिए तर्क जहां परमाणु भार और स्टोर कम से कम 1-रास्ता मेमोरी बैरियर के लिए संकलित करते हैं (और seq_cst ऑपरेशंस को फिर से व्यवस्थित नहीं कर सकते हैं: उदाहरण के लिए seq_cst स्टोर seq_cst लोड पास नहीं कर सकता है) यह है कि कोई भी लोड / दुकानों के बाद dummy2.loadनिश्चित रूप से अन्य धागे के बाद दिखाई देते हैं y.store। और इसी तरह दूसरे धागे के लिए, ... पहले y.load।)


आप https://godbolt.org/z/u3dTa8 पर विकल्प A, B, C के मेरे कार्यान्वयन के साथ खेल सकते हैं


1
C ++ मेमोरी मॉडल में StoreLoad reordering की कोई अवधारणा नहीं है, केवल सिंक्रोनाइज़-के साथ और पहले होता है। (और वास्तविक हार्डवेयर के लिए asm के विपरीत, गैर-परमाणु वस्तुओं पर डेटा दौड़ पर UB।) उन सभी वास्तविक कार्यान्वयनों पर, std::atomic_thread_fence(std::memory_order_seq_cst)जिनके बारे में मुझे पता है, एक पूर्ण अवरोध के लिए संकलित करता है, लेकिन चूंकि पूरी अवधारणा एक कार्यान्वयन विवरण है जो आपको नहीं मिलेगा मानक में इसका कोई उल्लेख नहीं है। (सीपीयू स्मृति मॉडल आमतौर पर कर रहे हैं क्या reorerings अनुक्रमिक स्थिरता के सापेक्ष अनुमति दी जाती है के रूप में परिभाषित जैसे 86 है seq-सीएसटी + एक दुकान बफर डब्ल्यू / अग्रेषण।)
पीटर Cordes

@PeterCordes धन्यवाद, मैं अपने लेखन में स्पष्ट नहीं हो सकता था। मैं आपको बताना चाहता हूं कि आपने "विकल्प ए" अनुभाग में क्या लिखा है। मुझे पता है कि मेरे प्रश्न का शीर्षक "स्टोरलोड" शब्द का उपयोग करता है, और यह कि "स्टोरलोड" एक पूरी तरह से अलग दुनिया से एक अवधारणा है। मेरी समस्या यह है कि इस अवधारणा को C ++ में कैसे मैप किया जाए। या अगर इसे सीधे मैप नहीं किया जा सकता है, तो मैंने जो लक्ष्य हासिल किया है उसे कैसे प्राप्त किया जाए: दोनों को बुलाया जाने से रोकें foo()और रोकें bar()
9

1
आप compare_exchange_*एक परमाणु बूल पर आरएमडब्ल्यू ऑपरेशन का उपयोग उसके मूल्य को बदलने के बिना कर सकते हैं (बस उसी मूल्य पर अपेक्षित और नया सेट करें)।
म्पोटर

1
@Fareanor और qbolec: atomic<bool>है exchangeऔर compare_exchange_weak। उत्तरार्द्ध का उपयोग कैस (सच्चा, सच्चा) या असत्य, असत्य का प्रयास करके डमी आरएमडब्ल्यू करने के लिए किया जा सकता है। यह या तो विफल हो जाता है या परमाणु मूल्य को स्वयं के साथ बदल देता है। (X86-64 asm में, वह ट्रिक lock cmpxchg16bयह है कि आप गारंटीकृत-परमाणु 16-बाइट लोड कैसे करते हैं, एक अलग ताला लेने की तुलना में अक्षम लेकिन कम बुरा है।)
पीटर कॉर्ड

1
@PeterCordes हाँ मुझे पता है कि ऐसा हो सकता है और न foo()ही bar()बुलाया जाएगा। मैं कोड के कई "वास्तविक दुनिया" तत्वों को नहीं लाना चाहता था, जिससे आपको लगता है कि "आपको समस्या है एक्स से बचने के लिए लेकिन आपको समस्या है वाई" तरह की प्रतिक्रियाएं। लेकिन, अगर किसी को यह जानने की जरूरत है कि पृष्ठभूमि की मंजिला क्या है: set()वास्तव में some_mutex_exit(), check()यह है try_enter_some_mutex(), y"कुछ वेटर हैं", foo()"किसी को भी जागने के बिना बाहर निकलें", bar()"वकप के लिए इंतजार कर रहा है" ... लेकिन, मैंने मना कर दिया इस डिज़ाइन पर यहाँ चर्चा करें - मैं इसे वास्तव में नहीं बदल सकता।
qbolec

जवाबों:


5

विकल्प ए और बी वैध समाधान हैं।

  • विकल्प A: यह वास्तव में मायने नहीं रखता है कि seq-cst बाड़ किसका अनुवाद करती है, C ++ मानक स्पष्ट रूप से परिभाषित करता है कि यह क्या गारंटी देता है। मैंने उन्हें इस पद पर बिठा दिया है : कब एक मैमोरी_ऑर्डर_सेक_कैंस बाड़ उपयोगी है?
  • विकल्प बी: हाँ, आपका तर्क सही है। किसी वस्तु पर सभी संशोधनों में एक ही कुल क्रम (संशोधन आदेश) होता है, इसलिए आप इसका उपयोग थ्रेड्स को सिंक्रनाइज़ करने और सभी दुष्प्रभावों की दृश्यता सुनिश्चित करने के लिए कर सकते हैं।

हालाँकि, विकल्प C मान्य नहीं है! एक सिंक्रनाइज़-इन रिलेशन केवल उसी ऑब्जेक्ट पर अधिग्रहण / रिलीज़-ऑपरेशन द्वारा स्थापित किया जा सकता है । आपके मामले में आपके पास दो पूरी तरह से अलग और अकर्मण्य वस्तुएं हैं dummy1और dummy2। लेकिन इनका उपयोग एक संबंध स्थापित करने से पहले नहीं किया जा सकता है। वास्तव में, चूंकि परमाणु चर विशुद्ध रूप से स्थानीय होते हैं (यानी, वे केवल एक धागे से कभी-कभी स्पर्श किए जाते हैं), संकलक उन्हें यथा-नियम के आधार पर हटाने के लिए स्वतंत्र है

अपडेट करें

विकल्प A:
मैं मानता हूं set()और check()कुछ परमाणु मूल्य पर काम करता हूं । तब हमारे पास निम्न स्थिति है (-> अनुक्रम से पहले दर्शाता है ):

  • set()-> fence1(seq_cst)->y.load()
  • y.store(true)-> fence2(seq_cst)->check()

तो हम निम्नलिखित नियम लागू कर सकते हैं:

परमाणु संचालन के लिए और बी एक परमाणु वस्तु एम पर , जहां एम को संशोधित करता है और बी इसकी कीमत लेता है, अगर memory_order_seq_cstबाड़ एक्स और वाई ऐसे हैं कि को एक्स से पहले अनुक्रमित किया जाता है , वाई को बी से पहले अनुक्रमित किया जाता है , और एस में एक्स प्री वाई होता है । तब B अपने संशोधन क्रम में M के A या बाद के संशोधन के प्रभावों को देखता है ।

Ie, या तो check()उस मान को संग्रहीत करता है setया y.load()लिखा हुआ मान देखता है y.store()(जिस पर परिचालन yभी उपयोग कर सकते हैं memory_order_relaxed)।

विकल्प C: सी ++ 17 मानक राज्यों [32.4.3, p1347]:

सभी परिचालनों पर एक ही कुल क्रम S होगाmemory_order_seq_cst , जो सभी प्रभावित स्थानों के लिए "आदेश से पहले" आदेश और संशोधन के आदेशों के अनुरूप है [...]

यहाँ महत्वपूर्ण शब्द "सुसंगत" है। तात्पर्य यह है कि यदि एक ऑपरेशन A होता है-एक ऑपरेशन B से पहले , तो A को S में B से पहले होना चाहिए । सिर्फ इसलिए कि कुछ आपरेशन: हालांकि, तार्किक निहितार्थ तो हम उलटा अनुमान नहीं लगा सकता, एक-तरफ़ा-सड़क है सी पछाड़ एक ऑपरेशन डी में एस मतलब यह नहीं है कि सी से पहले होता है विकास

विशेष रूप से, दो अलग-अलग वस्तुओं पर दो seq-cst संचालन का उपयोग संबंध से पहले होने के लिए नहीं किया जा सकता है, भले ही संचालन पूरी तरह से एस में आदेश दिए गए हों। यदि आप अलग-अलग वस्तुओं पर संचालन का आदेश देना चाहते हैं, तो आपको seq-cst का संदर्भ देना होगा। -fences (विकल्प A देखें)।


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

साभार @mpoeter क्या आप विकल्प ए के बारे में विस्तार से बता सकते हैं कि आपके उत्तर में कौन सी तीन गोलियां यहां लागू हैं? IIUC यदि y.load()प्रभाव नहीं देखता है y.store(1), तो हम नियमों से साबित कर सकते हैं कि S में, atomic_thread_fenceथ्रेड_ए थ्रेड_ बी से पहले atomic_thread_fenceहै। मैं जो नहीं देखता वह यह है कि इससे कैसे निष्कर्ष निकाला जाए कि set()साइड इफेक्ट दिखाई दे रहे हैं check()
क्यूबलेक

1
@qbolec: मैंने विकल्प ए के बारे में अधिक जानकारी के साथ अपने उत्तर को अपडेट किया है
मेटर

1
हां, एक स्थानीय seq-cst ऑपरेशन अभी भी सभी seq-cst संचालन पर एकल आदेश S का हिस्सा होगा । लेकिन S "केवल" होता है-पहले के क्रम और संशोधन के आदेशों के अनुरूप , यदि A होता है- B से पहले , तो A को S में B से पहले होना चाहिए । लेकिन उलटा, इसकी गारंटी नहीं है यानी, सिर्फ इसलिए कि एक पछाड़ बी में एस , हम अनुमान नहीं कर सकते हैं , कि एक होता है-पहले बी
म्पोटर

1
खैर, यह मानते हुए कि setऔर checkसमानांतर रूप से सुरक्षित रूप से निष्पादित किया जा सकता है, मैं शायद विकल्प ए के साथ जाऊंगा, खासकर अगर यह महत्वपूर्ण प्रदर्शन है, क्योंकि यह साझा चर पर विवाद से बचा जाता है y
म्पोटर

1

पहले उदाहरण में, y.load()पढ़ना 0 का मतलब यह नहीं है कि y.load()पहले होता है y.store(1)

हालांकि इसका मतलब यह है कि यह पहले से ही एकल कुल आदेश में है, जो नियम के लिए धन्यवाद है कि seq_cst लोड कुल क्रम में अंतिम seq_cst स्टोर का मूल्य या तो कुछ गैर-seq_cstst मान का रिटर्न देता है जो पहले नहीं होता है यह (जो इस मामले में मौजूद नहीं है)। इसलिए यदि y.store(1)पहले y.load()के कुल क्रम से पहले था , y.load()तो 1 लौटा होगा।

प्रमाण अभी भी सही है क्योंकि एकल कुल आदेश में एक चक्र नहीं है।

इस समाधान के बारे में कैसे?

std::atomic<int> x2{0},y{0};

void thread_a(){
  set();
  x2.store(1);
  if(!y.load()) foo();
}

void thread_b(){
  y.store(1);
  if(!x2.load()) bar();
}

ओपी की समस्या यह है कि मेरा "एक्स" पर कोई नियंत्रण नहीं है - यह रैपर मैक्रोज़ या कुछ के पीछे है और सीक-सीस्ट स्टोर / लोड नहीं हो सकता है। मैंने उस बेहतर को उजागर करने के लिए सवाल अपडेट किया।
पीटर कॉर्ड्स

@PeterCordes यह विचार एक और "x" बनाने के लिए था जिसका उस पर नियंत्रण है। मैं इसे स्पष्ट करने के लिए अपने उत्तर में "x2" का नाम बदलूंगा। मुझे यकीन है कि मुझे कुछ आवश्यकता याद आ रही है, लेकिन अगर केवल यह सुनिश्चित करना है कि फू () और बार () दोनों को बुलाया नहीं जाता है, तो यह संतुष्ट करता है।
टोमेक कज्जाका

इसलिए, if(false) foo();लेकिन मुझे लगता है कि ओपी ऐसा नहीं चाहता: पी दिलचस्प बिंदु लेकिन मुझे लगता है कि ओपी चाहता है कि सशर्त कॉल उन शर्तों के आधार पर हो जो वे निर्दिष्ट करते हैं!
पीटर कॉर्ड्स

1
हाय @TomekCzajka, नए समाधान का प्रस्ताव करने के लिए समय निकालने के लिए धन्यवाद। यह मेरे विशेष मामले में काम नहीं करेगा, क्योंकि यह महत्वपूर्ण दुष्प्रभावों को छोड़ देता है check()(वास्तविक दुनिया के अर्थ के लिए मेरे प्रश्न के लिए मेरी टिप्पणी देखें set,check,foo,bar)। मुझे लगता है कि यह if(!x2.load()){ if(check())x2.store(0); else bar(); }इसके बजाय काम कर सकता है ।
क्यूबेकेल

1

@mpoeter ने बताया कि विकल्प A और B सुरक्षित क्यों हैं।

वास्तविक कार्यान्वयन पर अभ्यास में, मुझे लगता है कि विकल्प ए std::atomic_thread_fence(std::memory_order_seq_cst)को थ्रेड ए में केवल एक की जरूरत है, बी की नहीं।

प्रैक्टिस में seq-cst स्टोर्स में एक पूर्ण मेमोरी बैरियर शामिल होता है, या AArch64 पर कम से कम बाद में अधिग्रहण या seq_cst लोड के साथ पुनः क्रम नहीं कर सकता है ( stlrअनुक्रमिक-रिलीज़ को ldarकैश से पहले पढ़ सकते हैं स्टोर बफर से निकल जाना है )।

सी ++ -> एएसएम मैपिंग में परमाणु स्टोर या परमाणु भार पर स्टोर बफर को सूखा देने की लागत डालने का विकल्प होता है। वास्तविक कार्यान्वयन के लिए समझदार विकल्प परमाणु भार को सस्ता करना है, इसलिए seq_cst स्टोर्स में एक पूर्ण अवरोध (स्टोरप्ले सहित) शामिल हैं। जबकि seq_cst लोड अधिकांश पर अधिग्रहित भार के समान हैं।

(लेकिन बिजली नहीं है; यहां तक ​​कि भार भी भारी-वजन सिंक की आवश्यकता है = उसी कोर पर अन्य एसएमटी थ्रेड्स से स्टोर-फॉरवर्डिंग को रोकने के लिए पूर्ण अवरोध की आवश्यकता होती है, जिससे आईआरआईडब्ल्यू पुन: व्यवस्थित हो सकता है, क्योंकि seq_cst को सभी थ्रेड्स के आदेश पर सहमत होने में सक्षम होने की आवश्यकता होती है सभी seq_cst ऑप्स। अलग धागे हमेशा अन्य धागे से एक ही क्रम में देखा जा में विभिन्न स्थानों के लिए विल दो परमाणु लिखता है? )

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


मुझे लगता है कि विकल्प सी के साथ वास्तविक समस्या यह है कि यह कुछ काल्पनिक पर्यवेक्षक कि पर निर्भर करता है हो सकता है सिंक्रनाइज़-साथ yऔर डमी आपरेशनों। और इस तरह हम उम्मीद करते हैं कि कंपाइलर उस ऑर्डर को संरक्षित करे जब एक बैरियर-आधारित आईएसए के लिए एसम बना रहा हो।

यह वास्तविक ISAs पर व्यवहार में सच होने जा रहा है; दोनों थ्रेड्स में एक पूर्ण अवरोध या समतुल्य शामिल हैं और संकलक एटमिक्स को अनुकूलित (अभी तक) नहीं करते हैं। लेकिन बेशक "एक बाधा आधारित ISA के लिए संकलन" आईएसओ सी ++ मानक का हिस्सा नहीं है। सुसंगत साझा कैश एक काल्पनिक पर्यवेक्षक है जो asm तर्क के लिए मौजूद है लेकिन ISO C ++ तर्क के लिए नहीं है।

काम करने के लिए विकल्प सी के लिए, हम की तरह एक आदेश देने की जरूरत है dummy1.store(13);/ y.load()/ set();(के रूप में धागा बी द्वारा देखा) कुछ आईएसओ सी का उल्लंघन करने के ++ नियम

इन कथनों को चलाने वाले सूत्र को ऐसा व्यवहार करना है जैसे set() पहले निष्पादित किया गया हो (क्योंकि पहले से अनुक्रमित हो)। यह ठीक है, रनटाइम मेमोरी ऑर्डरिंग और / या संचालन के समय को पुन: व्यवस्थित करने का संकलन अभी भी कर सकता है।

दो seq_cst ऑप्स d1=13और yअनुक्रम से पहले (प्रोग्राम ऑर्डर) के अनुरूप हैं। set()seq_cst ops के लिए आवश्यक-से-मौजूद वैश्विक क्रम में भाग नहीं लेता क्योंकि यह seq_cst नहीं है।

थ्रेड बी सिंक्रनाइज़ नहीं होता है-setd1=13 डमी 1.स्टोर के साथ इसलिए ऐसा नहीं होता है-इससे पहले कि आवेदन करने के सापेक्ष कोई आवश्यकता हो , भले ही वह असाइनमेंट एक रिलीज ऑपरेशन हो।

मुझे कोई अन्य संभावित नियम उल्लंघन नहीं दिख रहा है; मुझे ऐसा कुछ भी नहीं मिल रहा है जो setअनुक्रम-पहले के अनुरूप होना आवश्यक है d1=13

"Dummy1.store रिलीज़ सेट ()" तर्क दोष है। यह आदेश केवल एक वास्तविक पर्यवेक्षक के लिए लागू होता है जो इसके साथ, या asm में सिंक्रनाइज़ करता है। जैसा कि @mpoeter ने उत्तर दिया है, seq_cst कुल ऑर्डर का अस्तित्व संबंध बनाने से पहले या होने वाला नहीं है, और यह एकमात्र ऐसी चीज है जो औपचारिक रूप से seq_cst के बाहर ऑर्डर करने की गारंटी देती है।

सुसंगत साझा कैश के साथ "सामान्य" सीपीयू का कोई भी प्रकार जहां यह पुन: प्रसारण वास्तव में रनटाइम पर हो सकता है वह प्रशंसनीय नहीं लगता है। (लेकिन अगर एक संकलक निकाल सकता है dummy1और dummy2फिर स्पष्ट रूप से हमें एक समस्या होगी, और मुझे लगता है कि यह मानक द्वारा अनुमत है।)

लेकिन चूंकि सी ++ मेमोरी मॉडल को स्टोर बफर, साझा सुसंगत कैश, या अनुमत पुनरावर्ती के लिटमस परीक्षणों के संदर्भ में परिभाषित नहीं किया गया है, इसलिए पवित्रता के लिए आवश्यक चीजें औपचारिक रूप से सी ++ नियमों द्वारा आवश्यक नहीं हैं। यह शायद जानबूझकर भी seq_cst वैरिएबल को दूर करने की अनुमति देता है जो थ्रेड प्राइवेट होते हैं। (वर्तमान संकलक ऐसा नहीं करते हैं, निश्चित रूप से, या परमाणु वस्तुओं का कोई अन्य अनुकूलन।)

एक कार्यान्वयन जहां एक धागा वास्तव में set()अंतिम देख सकता है, जबकि दूसरा set()पहले ध्वनियों को देख सकता है । पावर भी ऐसा नहीं कर सकता था; दोनों seq_cst लोड और स्टोर में POWER के लिए पूर्ण अवरोध शामिल हैं। (मैंने टिप्पणियों में सुझाव दिया था कि IRIW reordering यहां प्रासंगिक हो सकता है; C ++ के acq / rel के नियम इसे समायोजित करने के लिए काफी कमजोर हैं, लेकिन सिंक्रोनाइज़-आउट या अन्य होने से पहले गारंटी की कुल कमी-स्थिति किसी भी HW से बहुत कमजोर है। )

C ++ गैर-seq_cst के लिए कुछ भी गारंटी नहीं देता है जब तक कि वास्तव में एक पर्यवेक्षक न हो, और उसके बाद केवल उस पर्यवेक्षक के लिए। एक के बिना हम Schroedinger के बिल्ली क्षेत्र में हैं। या, अगर जंगल में दो पेड़ गिरते हैं, तो क्या एक के बाद एक पेड़ गिरते हैं? (यदि यह एक बड़ा जंगल है, तो सामान्य सापेक्षता कहती है कि यह पर्यवेक्षक पर निर्भर करता है और साथ ही साथ सार्वभौमिक अवधारणा नहीं है।)


@mpoeter ने यह भी सुझाव दिया कि एक संकलक भी डमी लोड और स्टोर संचालन को हटा सकता है, यहां तक ​​कि seq_cst ऑब्जेक्ट पर भी।

मुझे लगता है कि यह सही हो सकता है जब वे साबित कर सकते हैं कि कुछ भी एक ऑपरेशन के साथ सिंक्रनाइज़ नहीं हो सकता है। एक संकलक की तरह, जो देख सकता है कि dummy2फ़ंक्शन से बच नहीं सकता है शायद उस seq_cst लोड को हटा सकता है।

इसका कम से कम एक वास्तविक दुनिया परिणाम है: यदि AArch64 के लिए संकलन किया जाता है, तो इससे पहले के seq_cst स्टोर को बाद में आराम से संचालन के साथ फिर से चालू करने की अनुमति होगी, जो seq_cst स्टोर के साथ संभव नहीं होगा + स्टोर बफर लोड करने से पहले किसी भी बाद में भार निष्पादित हो सकता है।

बेशक वर्तमान संकलक एटमिक्स का अनुकूलन नहीं करते हैं, भले ही आईएसओ सी ++ इसे मना नहीं करता है; यह मानक समिति के लिए एक अनसुलझी समस्या है

यह मुझे सोचने की अनुमति है क्योंकि C ++ मेमोरी मॉडल में एक अंतर्निहित पर्यवेक्षक या एक आवश्यकता नहीं है जो सभी थ्रेड्स ऑर्डर करने पर सहमत होते हैं। यह सुसंगत कैश के आधार पर कुछ गारंटी प्रदान करता है, लेकिन इसके लिए सभी थ्रेड्स को एक साथ होने की दृश्यता की आवश्यकता नहीं होती है।


अच्छा सारांश! मैं मानता हूँ कि में अभ्यास यह शायद अगर केवल थ्रेड एक seq-सीएसटी बाड़ था पर्याप्त होगा। हालाँकि, C ++ मानक के आधार पर हमारे पास आवश्यक गारंटी नहीं होगी कि हम नवीनतम मूल्य देखें set(), इसलिए मैं अभी भी थ्रेड बी में बाड़ का उपयोग करूंगा। मुझे लगता है कि एक seq-cst बाड़ के साथ एक आराम से स्टोर है, वैसे भी seq-cst-store के रूप में लगभग एक ही कोड उत्पन्न होगा।
15

@ चैंपियन: हाँ, मैं केवल अभ्यास के बारे में बात कर रहा था, औपचारिक रूप से नहीं। उस अनुभाग के अंत में एक नोट जोड़ा गया। और हाँ, अधिकांश ISAs पर व्यवहार में मुझे लगता है कि एक seq_cst स्टोर आमतौर पर सिर्फ सादे स्टोर (आराम से) + एक बाधा है। या नहीं; पावर पर seq-cst स्टोर स्टोर sync से पहले (भारी वजन) करता है, उसके बाद कुछ भी नहीं। godbolt.org/z/mAr72P लेकिन seq-cst भार दोनों तरफ कुछ बाधाओं की जरूरत है।
पीटर कॉर्ड्स

1

ISO मानक std :: mutex में केवल अधिग्रहण और रिलीज ऑर्डर देने की गारंटी है, seq_cst की नहीं।

लेकिन "seq_cst ऑर्डर" करने के लिए कुछ भी गारंटी नहीं है, क्योंकि seq_cstकिसी भी ऑपरेशन की संपत्ति नहीं है।

seq_cstकिसी दिए गए कार्यान्वयन std::atomicया वैकल्पिक परमाणु वर्ग के सभी कार्यों की गारंटी है । जैसे, आपका प्रश्न निराधार है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.