2012-09-12 12 views
7

मैं कुछ लॉकलेस कोड की शुद्धता पर जुनून लगा रहा हूं, और मैं वास्तव में किसी भी इनपुट की सराहना करता हूं। मेरा प्रश्न सी ++ 11 के मेमोरी मॉडल में & रिलीज सेमेन्टिक्स प्राप्त करके कुछ आवश्यक इंटर-थ्रेड सिंक्रनाइज़ेशन को प्राप्त करने के तरीके के बारे में है। मेरे प्रश्न से पहले, कुछ पृष्ठभूमि ...एमवीसीसी कार्यान्वयन में लॉकलेस रीडर/राइटर सिंक्रनाइज़ेशन

MVCC में, लेखक पुराने ऑब्जेक्ट संस्करणों के पाठकों को प्रभावित किए बिना किसी ऑब्जेक्ट का नया संस्करण स्थापित कर सकता है। हालांकि, यदि कोई लेखक किसी ऑब्जेक्ट का एक नया संस्करण स्थापित करता है जब उच्च-क्रमांकित टाइमस्टैम्प वाला पाठक पहले से ही पुराने संस्करण का संदर्भ प्राप्त कर चुका है, तो लेखक लेनदेन को & वापस लेना होगा। यह serializable स्नैपशॉट अलगाव को संरक्षित करना है (इसलिए यह है जैसे कि सभी सफल लेन-देन दूसरे के बाद टाइमस्टैम्प ऑर्डर में निष्पादित किए गए हैं)। पाठकों को लिखने के कारण कभी भी पुनः प्रयास नहीं करना पड़ता है, लेकिन लेखकों को & वापस लेना पड़ सकता है यदि उनकी गतिविधि "उच्चतम संख्या वाले टाइमस्टैम्प वाले पाठकों के नीचे से बाहर निकलती है"। इस बाधा को लागू करने के लिए, रीड-टाइमस्टैम्प का उपयोग किया जाता है। विचार यह है कि एक पाठक किसी ऑब्जेक्ट के रीड-टाइमस्टैम्प को संदर्भ प्राप्त करने से पहले अपने टाइमस्टैम्प पर अपडेट करता है, और एक लेखक रीड-टाइमस्टैम्प को यह देखने के लिए जांच करेगा कि उस ऑब्जेक्ट के नए संस्करण के साथ आगे बढ़ना ठीक है या नहीं।

मान लीजिए कि दो लेनदेन हैं: टी 1 (लेखक) और टी 2 (पाठक) जो अलग थ्रेड में चल रहे हैं।

void 
DataStore::update(CachedObject* oldObject, CachedObject* newObject) 
{ 
    . 
    . 
    . 
    COcontainer* container = oldObject->parent(); 
    tid_t newTID = newObject->revision(); 
    container->setObject(newObject); 
    tid_t* rrp = &container->readRevision; 
    tid_t rr = __atomic_load_n(rrp, __ATOMIC_ACQUIRE); 
    while (true) 
    { 
     if (rr > newTID) throw TransactionRetryEx(); 
     if (__atomic_compare_exchange_n(
      rrp, 
      &rr, 
      rr, 
      false, 
      __ATOMIC_RELEASE, 
      __ATOMIC_RELAXED) 
     { 
      break; 
     } 
    } 
} 

टी 2 (पाठक) करता है:

टी 1 (लेखक) करता

CachedObject* 
Transaction::onRead(CachedObject* object) 
{ 
    tid_t tid = Transaction::mine()->tid(); 
    COcontainer* container = object->parent(); 
    tid_t* rrp = &container->readRevision; 
    tid_t rr = __atomic_load_n(rrp, __ATOMIC_ACQUIRE); 
    while (rr < tid) 
    { 
     if (__atomic_compare_exchange_n(
      rrp, 
      &rr, 
      tid, 
      false, 
      __ATOMIC_ACQUIRE, 
      __ATOMIC_ACQUIRE)) 
     { 
      break; 
     } 
    } 
    // follow the chain of objects to find the newest one this transaction can use 
    object = object->newest(); 
    // caller can use object now 
    return object; 
} 

यह मैं चिंतित हूँ स्थिति का सरल सारांश है के बारे में:

 A B C 
<----*----*----*----> 
    timestamp order 

A: old object's timestamp 
B: new object's timestamp (T1's timestamp) 
C: "future" reader's timestamp (T2's timestamp) 

* If [email protected] reads [email protected], [email protected] must be rolled back. 

यदि टी 2 शुरू होने से पहले टी 1 पूरी तरह से निष्पादित हो जाता है (और टी 1 के प्रभाव टी 2 के लिए पूरी तरह से दृश्यमान होते हैं), तो कोई समस्या नहीं है। टी 2 टी 1 द्वारा स्थापित ऑब्जेक्ट संस्करण का संदर्भ प्राप्त करेगा, जिसका उपयोग यह कर सकता है क्योंकि टी 1 का टाइमस्टैम्प टी 2 से कम है। (एक लेनदेन "अतीत से" वस्तुओं को पढ़ सकता है लेकिन यह "भविष्य में सहकर्मी" नहीं कर सकता है)।

यदि टी 1 शुरू होने से पहले टी 2 पूरी तरह से निष्पादित हो जाता है (और टी 2 के प्रभाव टी 1 के लिए पूरी तरह से दृश्यमान होते हैं), तो कोई समस्या नहीं है। टी 1 देखेंगे कि "भविष्य से" एक लेनदेन ने ऑब्जेक्ट के पुराने संस्करण को संभावित रूप से पढ़ा है। इसलिए, टी 1 वापस लुढ़काया जाएगा और काम के प्रदर्शन को पुनः प्रयास करने के लिए एक नया लेनदेन बनाया जाएगा।

(बेशक) मुसीबत सही व्यवहार की गारंटी जब T1 और टी 2 एक समय पर है। यह बहुत ही बस दौड़ की स्थिति को खत्म करने की म्युटेक्स उपयोग करने के लिए आसान होगा, लेकिन मैं केवल ताले के साथ एक समाधान को स्वीकार करेंगे अगर मैं विश्वास हो गया है कि वहाँ कोई दूसरा रास्ता नहीं था। मुझे यकीन है कि यह & अधिग्रहण सी ++ 11 के रिलीज मेमोरी मॉडल के साथ ऐसा करना संभव होना चाहिए। मैं कुछ जटिलता के साथ ठीक हूँ, इसलिए जब तक मैं संतुष्ट हो सकते हैं कि कोड सही है। मैं वास्तव में पाठकों को जितनी जल्दी हो सके दौड़ना चाहता हूं, जो एमवीसीसी की मुख्य बिक्री सुविधा है।

सवाल:

1. ऊपर (आंशिक) कोड को देखते हुए, आपको लगता है एक रेस स्थिति, मौजूद है ताकि टी 1 वापस लुढ़का जा करने के लिए एक मामले में जहां में विफल हो सकता है (throw TransactionRetryEx() के माध्यम से) है ऑब्जेक्ट के पुराने संस्करण का उपयोग करने के लिए टी 2 आय प्राप्त करता है?

2. यदि कोड गलत है, तो समझाएं और क्यों इसे सही बनाने के लिए सामान्य मार्गदर्शन प्रदान करें।

3. यदि कोड सही दिखता है, तो भी आप देख सकते हैं कि यह और अधिक कुशल कैसे हो सकता है?

DataStore::update() में मेरे तर्क है कि अगर __atomic_compare_exchange_n() करने के लिए कॉल सफल होता है, तो इसका मतलब है "विरोधी" पाठक धागा अभी तक पढ़ा-टाइमस्टैम्प अपडेट नहीं किया गया है, और इसलिए यह भी वस्तु संस्करणों की श्रृंखला चल नहीं किया है नए सुलभ संस्करण को खोजने के लिए जो अभी स्थापित किया गया था।

मैं के बारे में किताब "Transactional Information Systems: Theory, Algorithms, and the Practice of Concurrency Control and Recovery" खरीदने के लिए कर रहा हूँ, लेकिन मैंने सोचा कि मैं भी आपको परेशान करेंगे: डि लगता है कि मैं किताब जल्दी ही खरीदा जाना चाहिए था, लेकिन मैं यह भी काफी यकीन है कि मैं कुछ भी है कि अमान्य होगा सीखना नहीं होगा मेरे काम का एक बड़ा हिस्सा।

मुझे आशा है कि मैंने उत्तर देने के लिए पर्याप्त जानकारी दी है। अगर मैं इसे रचनात्मक आलोचना प्राप्त करता हूं तो मैं इसे और अधिक स्पष्ट करने के लिए अपने प्रश्न को खुशी से संपादित करूंगा। यदि यह प्रश्न (या ऐसा कुछ भी) पहले से ही & से पूछा गया है, तो यह बहुत अच्छा होगा।

धन्यवाद!

+0

आप वास्तव में किस लेनदेन अलगाव स्तर का लक्ष्य कर रहे हैं? –

+0

सीरियलज़ेबल अलगाव (उदाहरण के लिए PostgreSQL में "पढ़ा गया" अलगाव स्तर नहीं है)। –

+0

COcontainer की भूमिका वास्तव में क्या है, क्या इसमें सभी ऑब्जेक्ट्स या किसी दिए गए ऑब्जेक्ट के सभी संस्करण शामिल हैं? यह किस प्रकार की लॉकिंग या लॉकलेस हैंडलिंग का उपयोग करता है? काउंटर के साथ आप लेनदेन आईडी/टाइमस्टैम्प कैसे उत्पन्न करते हैं? –

उत्तर

1

यह जटिल है, मैं अभी तक 1. और 2. के लिए कुछ भी नहीं कह सकता, लेकिन 3 के बारे में मैं कुछ देखा है:

जब __atomic_compare_exchange_n झूठी देता है, तो * RRP के वर्तमान मूल्य में लिखा है आरआर, तो लूप के भीतर __atomic_load() एस दोनों अनावश्यक हैं (टी 2 में बस इसे फेंक दें, टी 1 में इसे टी 2 में लूप से पहले एक बार करें)।

सामान्य टिप्पणी के रूप में, संभवतः अधिग्रहण/रिलीज के बारे में सोचना आवश्यक नहीं है जब तक कि एल्गोरिदम में सबकुछ समाप्त नहीं हो जाता है; तो आप यह देखने के लिए जांच सकते हैं कि "हर जगह" मेमोरी बाधा कितनी मजबूत है।

+0

धन्यवाद, यह एक अच्छा अवलोकन है। जब मैं झूठी वापसी करता हूं तो मैं __atomic_compare_exchange_n के व्यवहार के लिए उचित रूप से लेखांकन नहीं कर रहा था। मैंने कोड को इसके लिए खाते में बदल दिया। मैंने कोड को और अधिक आराम से ऑर्डर करने के लिए भी बदल दिया (__ATOMIC_ACQ_REL को __ATOMIC_ACQUIRE के साथ एक स्थान पर बदल दिया, और इसे किसी अन्य स्थान पर __ATOMIC_RELEASE के साथ बदल दिया)। स्मृति मॉडल पैरामीटर को कम से कम एक स्थान पर शायद __ATOMIC_RELAXED में बदला जा सकता है, लेकिन मैं इसे अभी जाने दूंगा। –

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