2010-11-12 21 views
11

पर परमाणु काउंटर मुझे बस एक पल होना चाहिए, क्योंकि यह आसान होना चाहिए लेकिन मुझे यह सही काम नहीं कर रहा है।जीसीसी

जीसीसी में परमाणु काउंटर को लागू करने का सही तरीका क्या है?

यानी मैं एक काउंटर चाहता हूं जो शून्य से 4 तक चलता है और धागा सुरक्षित है।

मैं यह कर रहा था (जो आगे एक कक्षा में लपेटा जाता है, लेकिन यहाँ नहीं)

static volatile int _count = 0; 
const int limit = 4; 

int get_count(){ 
    // Create a local copy of diskid 
    int save_count = __sync_fetch_and_add(&_count, 1); 
    if (save_count >= limit){ 
     __sync_fetch_and_and(&_count, 0); // Set it back to zero 
    } 
    return save_count; 
} 

लेकिन यह 1 से 1 से चल रहा है - तो 4 समावेशी शून्य करने के लिए चारों ओर।
इसे 0 - 3 से जाना चाहिए। आम तौर पर मैं एक मॉड ऑपरेटर के साथ काउंटर करता हूं लेकिन मैं नहीं जानता कि सुरक्षित तरीके से यह कैसे करें।

शायद यह संस्करण बेहतर है। क्या आप इसके साथ कोई समस्या देख सकते हैं, या एक बेहतर समाधान प्रदान कर सकते हैं।

int get_count(){ 
    // Create a local copy of diskid 
    int save_count = _count; 
    if (save_count >= limit){ 
     __sync_fetch_and_and(&_count, 0); // Set it back to zero 
     return 0; 
    } 

    return save_count; 
} 

असल में, मुझे यह इंगित करना चाहिए कि यह बिल्कुल महत्वपूर्ण नहीं है कि प्रत्येक थ्रेड को एक अलग मूल्य मिलता है। यदि दो थ्रेड एक ही समय में एक ही मूल्य को पढ़ने के लिए हुआ तो यह कोई समस्या नहीं होगी। लेकिन वे किसी भी समय सीमा से अधिक नहीं हो सकते हैं।

उत्तर

13

आपका कोड परमाणु नहीं है (और आपका दूसरा get_count काउंटर वैल्यू भी बढ़ाता नहीं है)!

कहें count शुरुआत में 3 है और दो धागे एक साथ get_count पर कॉल करते हैं। उनमें से एक को अपना परमाणु जोड़ा पहले मिलेगा और count से 4 तक बढ़ाएगा। यदि दूसरा धागा पर्याप्त तेज़ है, तो यह पहले थ्रेड को शून्य पर रीसेट करने से पहले इसे 5 तक बढ़ा सकता है।

इसके अलावा, आपके रैपरराउंड प्रोसेसिंग में, आप count से 0 पर रीसेट करते हैं लेकिन save_count नहीं। यह स्पष्ट रूप से नहीं है कि क्या इरादा है।

यह सबसे आसान है अगर limit 2. की एक शक्ति है, कभी कमी अपने आप ऐसा नहीं करते हैं तो बस का उपयोग करें

return (unsigned) __sync_fetch_and_add(&count, 1) % (unsigned) limit; 

या वैकल्पिक रूप से

return __sync_fetch_and_add(&count, 1) & (limit - 1); 

यह केवल मंगलाचरण प्रति एक परमाणु आपरेशन करता है , सुरक्षित और बहुत सस्ता है। सामान्य सीमाओं के लिए, आप अभी भी % का उपयोग कर सकते हैं, लेकिन अगर काउंटर कभी बहती है तो वह अनुक्रम को तोड़ देगा। आप 64-बिट मान का उपयोग करने का प्रयास कर सकते हैं (यदि आपका प्लेटफार्म 64-बिट परमाणुओं का समर्थन करता है) और उम्मीद है कि यह कभी भी बहती न हो; हालांकि यह एक बुरा विचार है। ऐसा करने का उचित तरीका एक परमाणु तुलना-विनिमय संचालन का उपयोग कर रहा है। आप यह करते हैं:

int old_count, new_count; 
do { 
    old_count = count; 
    new_count = old_count + 1; 
    if (new_count >= limit) new_count = 0; // or use % 
} while (!__sync_bool_compare_and_swap(&count, old_count, new_count)); 

यह दृष्टिकोण अधिक जटिल अनुक्रमों और अद्यतन संचालन को भी सामान्यीकृत करता है।

उस ने कहा, इस प्रकार का लॉकलेस ऑपरेशन सही होने के लिए मुश्किल है, कुछ डिग्री के लिए अपरिभाषित व्यवहार पर निर्भर करता है (सभी मौजूदा कंपाइलर्स यह अधिकार प्राप्त करते हैं, लेकिन सी ++ 0x से पहले कोई सी/सी ++ मानक वास्तव में अच्छी तरह परिभाषित नहीं है मेमोरी मॉडल) और तोड़ना आसान है। मैं एक साधारण म्यूटेक्स/लॉक का उपयोग करने की सलाह देता हूं जबतक कि आपने इसे प्रोफाइल नहीं किया है और यह एक बाधा बन गया है।

+0

चाहे __sync_fetch_and_add "प्रति आमंत्रण पर एक परमाणु ऑपरेशन करता है" सीपीयू पर निर्भर करता है - प्रश्न में निर्दिष्ट नहीं है। यह आपकी तुलना-और-स्वैप दृष्टिकोण के अनुसार लागू किया जा सकता है, जो कि मैंने अतीत में सूर्य हार्डवेयर पर उपयोग किया है (ठीक है, मेरे पूर्व सहयोगी के कार्यान्वयन, जिसका नाम "atomic_robin" :-)) है। –

+0

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

+0

हे धन्यवाद। यह वही है जो मैं बाद में हूं। मुझे पता नहीं है कि मैं अपने दूसरे समाधान के साथ क्या सोच रहा था। मुझे लगता है कि यह नींद की कमी थी जिसने मुझे इतना अच्छा कोड लिखा था। – Matt

0

शुद्ध सी में परमाणु कुछ भी बनाना असंभव है, यहां तक ​​कि volatile के साथ भी। आपको एएसएम चाहिए सी 1 एक्स में विशेष परमाणु प्रकार होंगे, लेकिन तब तक आप एएसएम के साथ फंस गए हैं।

+0

क्षमा करें मुझे इसे सी के रूप में टैग नहीं करना चाहिए था। यह सी ++ है, हालांकि c1x का उपयोग नहीं कर रहा है। – Matt

+3

शुद्ध सी, निश्चित है, लेकिन वह जीसीसी भी टैग किया गया है, इसलिए intrinsics/buildins सबसे अच्छा विकल्प हैं। –

+1

दरअसल, मेरा बुरा। –

0

आपके पास दो समस्याएं हैं।

__sync_fetch_and_addपिछले मूल्य (जैसे कि, जोड़ने से पहले) वापस आ जाएगी। तो उस चरण में जहां _count 3 हो जाता है, आपका स्थानीय save_count वैरिएबल 2 वापस प्राप्त कर रहा है। तो 3 के रूप में वापस आने से पहले आपको वास्तव में _count4 तक बढ़ाना होगा।

लेकिन इसके शीर्ष पर भी, आप विशेष रूप से >= 4 होने के लिए इसे 0 पर रीसेट करने से पहले देख रहे हैं। यह केवल गलत सीमा का उपयोग करने का एक प्रश्न है यदि आप केवल इसे उच्च के रूप में प्राप्त करने के लिए देख रहे हैं तीन के रूप में।

2

आप भाग्यशाली हैं, क्योंकि आप जो रेंज चाहते हैं वह बिल्कुल 2 बिट्स में फिट हो जाती है।

आसान समाधान: अस्थिर परिवर्तनीय हमेशा के लिए गिनती करें। लेकिन इसे पढ़ने के बाद, केवल निम्नतम दो बिट्स (val & 3) का उपयोग करें। 0-3 से प्रेस्टो, परमाणु काउंटर।

+0

या बस – Inverse

+0

@ इनवर्क्स का उपयोग करें: मोड बहुत धीमा हो सकता है ... और एक सुरक्षित शर्त है। –

+0

यह एक अच्छा मुद्दा है। हालांकि मुझे मनमाने ढंग से सीमा के मूल्य की आवश्यकता है। यह एक विन्यास फाइल से आता है। – Matt