2013-10-17 6 views
5

AFAIK सी ++ एटोमिक्स (<atomic>) परिवार 3 लाभ प्रदान करते हैं:सी ++ एटोमिक्स और पार धागा दृश्यता

  • आदिम निर्देश अविभाज्यता (कोई गंदा पढ़ता है),
  • स्मृति आदेश (दोनों, सीपीयू और संकलक के लिए) और
  • क्रॉस-थ्रेड दृश्यता/परिवर्तन प्रसार।

और मुझे तीसरी गोली के बारे में निश्चित नहीं है, इस प्रकार निम्नलिखित उदाहरण देखें।

#include <atomic> 

std::atomic_bool a_flag = ATOMIC_VAR_INIT(false); 
struct Data { 
    int x; 
    long long y; 
    char const* z; 
} data; 

void thread0() 
{ 
    // due to "release" the data will be written to memory 
    // exactly in the following order: x -> y -> z 
    data.x = 1; 
    data.y = 100; 
    data.z = "foo"; 
    // there can be an arbitrary delay between the write 
    // to any of the members and it's visibility in other 
    // threads (which don't synchronize explicitly) 

    // atomic_bool guarantees that the write to the "a_flag" 
    // will be clean, thus no other thread will ever read some 
    // strange mixture of 4bit + 4bits 
    a_flag.store(true, std::memory_order_release); 
} 

void thread1() 
{ 
    while (a_flag.load(std::memory_order_acquire) == false) {}; 
    // "acquire" on a "released" atomic guarantees that all the writes from 
    // thread0 (thus data members modification) will be visible here 
} 

void thread2() 
{ 
    while (data.y != 100) {}; 
    // not "acquiring" the "a_flag" doesn't guarantee that will see all the 
    // memory writes, but when I see the z == 100 I know I can assume that 
    // prior writes have been done due to "release ordering" => assert(x == 1) 
} 

int main() 
{ 
    thread0(); // concurrently 
    thread1(); // concurrently 
    thread2(); // concurrently 

    // join 

    return 0; 
} 

सबसे पहले, कृपया कोड में मेरी धारणाओं को मान्य करें (विशेष रूप से thread2)।

दूसरा, मेरे सवालों हैं:

  1. a_flag कैसे लिख सकता हूँ करता अन्य कोर तक पहुंचने में?

  2. std::atomic अन्य कोर कैश के साथ लेखक कैश में a_flag सिंक्रनाइज़ करता है (MESI का उपयोग कर, या कुछ और), या प्रचार स्वचालित है?

  3. मानते हैं कि किसी विशेष मशीन पर ध्वज को लिखना परमाणु (x86 पर int_32 लगता है) और हमारे पास सिंक्रनाइज़ करने के लिए कोई निजी स्मृति नहीं है (हमारे पास केवल ध्वज है) क्या हमें परमाणुओं का उपयोग करने की आवश्यकता है?

  4. को ध्यान में लेते हुए सबसे लोकप्रिय सीपीयू आर्किटेक्चर (x86, x64, एआरएम v.whatever, IA-64), क्रॉस-कोर दृश्यता (मैं अब नहीं पर विचार reorderings हूँ) स्वत: (परन्तु संभवत: देरी) है, या आपको डेटा के किसी भी हिस्से को प्रसारित करने के लिए विशिष्ट आदेश जारी करने की आवश्यकता है?

उत्तर

2
  1. कोर खुद को कोई फर्क नहीं पड़ता। सवाल यह है कि "सभी कोर एक ही मेमोरी अपडेट अंततः कैसे देखते हैं", जो आपके हार्डवेयर आपके लिए कुछ करता है (उदाहरण के लिए कैश कोहेन्सी प्रोटोकॉल)। केवल एक स्मृति है, इसलिए मुख्य चिंता कैशिंग है, जो हार्डवेयर की एक निजी चिंता है।

  2. यह प्रश्न अस्पष्ट लगता है। क्या मायने रखती अधिग्रहण रिलीज जोड़ी लोड और a_flag की दुकान है, जो एक तुल्यकालन बिंदु है और thread0 और thread1 के प्रभाव (पहले दुकान होता है-पहले सब कुछ एक निश्चित क्रम में प्रदर्शित करने के thread0 में यानी सब कुछ का कारण बनता है द्वारा गठित है thread1 में लूप के बाद)।

  3. हां, अन्यथा आपके पास सिंक्रनाइज़ेशन बिंदु नहीं होगा।

  4. आपको सी ++ में किसी भी "कमांड" की आवश्यकता नहीं है। सी ++ इस तथ्य से भी अवगत नहीं है कि यह किसी विशेष प्रकार के सीपीयू पर चल रहा है। आप शायद पर्याप्त कल्पना के साथ रुबिक के घन पर एक सी ++ प्रोग्राम चला सकते हैं। एक सी ++ कंपाइलर सी ++ मेमोरी मॉडल द्वारा वर्णित सिंक्रनाइज़ेशन व्यवहार को लागू करने के लिए आवश्यक निर्देश चुनता है, और x86 पर जिसमें निर्देश लॉक उपसर्ग और मेमोरी बाड़ जारी करने के साथ-साथ निर्देशों को पुन: निर्देशित करना भी शामिल नहीं है।चूंकि x86 में दृढ़ता से आदेश दिया गया मेमोरी मॉडल है, इसलिए उपरोक्त कोड को निष्क्रिय, गलत परमाणु के बिना गलत अतिरिक्त कोड का उत्पादन करना चाहिए।

  5. कोड में thread2 होने के कारण पूरे कार्यक्रम को अनिश्चित व्यवहार किया जाता है।


बस मस्ती के लिए, और पता चलता है कि बाहर काम कर अपने आप को edifying हो सकता है के लिए क्या हो रहा है, मैं तीन रूपों में कोड संकलित। (मैंने एक glbbal int x जोड़ा और thread1 में मैंने x = data.y; जोड़ा)।

मोल/रिलीज: (अपने कोड)

thread0: 
    mov DWORD PTR data, 1 
    mov DWORD PTR data+4, 100 
    mov DWORD PTR data+8, 0 
    mov DWORD PTR data+12, OFFSET FLAT:.LC0 
    mov BYTE PTR a_flag, 1 
    ret 

thread1: 
.L14: 
    movzx eax, BYTE PTR a_flag 
    test al, al 
    je .L14 
    mov eax, DWORD PTR data+4 
    mov DWORD PTR x, eax 
    ret 

क्रमिक रूप से संगत: (स्पष्ट आदेश देने को दूर)

thread0: 
    mov eax, 1 
    mov DWORD PTR data, 1 
    mov DWORD PTR data+4, 100 
    mov DWORD PTR data+8, 0 
    mov DWORD PTR data+12, OFFSET FLAT:.LC0 
    xchg al, BYTE PTR a_flag 
    ret 

thread1: 
.L14: 
    movzx eax, BYTE PTR a_flag 
    test al, al 
    je .L14 
    mov eax, DWORD PTR data+4 
    mov DWORD PTR x, eax 
    ret 

"अनुभवहीन": (बस bool का प्रयोग करके)

thread0: 
    mov DWORD PTR data, 1 
    mov DWORD PTR data+4, 100 
    mov DWORD PTR data+8, 0 
    mov DWORD PTR data+12, OFFSET FLAT:.LC0 
    mov BYTE PTR a_flag, 1 
    ret 

thread1: 
    cmp BYTE PTR a_flag, 0 
    jne .L3 
.L4: 
    jmp .L4 
.L3: 
    mov eax, DWORD PTR data+4 
    mov DWORD PTR x, eax 
    ret 

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

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