8

मैं एक अंगूठी बफर कार्यान्वयन (या स्यूडोकोड) सी में निम्नलिखित विशेषताओं के साथ रहा हूँ के लिए खोज रहे:सी में सही अंगूठी बफर कार्यान्वयन

  • कई निर्माता एकल उपभोक्ता पैटर्न (MPSC)
  • उपभोक्ता ब्लॉक खाली
  • उत्पादकों पर पर ब्लॉक पूर्ण
  • ताला मुक्त (मैं उच्च विवाद उम्मीद)

अब तक मैं मैं केवल एसपीएससी बफर के साथ काम कर रहा हूं - प्रति निर्माता एक - लेकिन मैं अपने सभी इनपुट बफर (और शायद मेरे सिस्टम में कुछ मार्शलिंग थ्रेड से छुटकारा पाने के लिए) पर नए डेटा की जांच करने के लिए उपभोक्ता की लगातार कताई से बचना चाहता हूं।

मैं इंटेल मशीनों पर लिनक्स के लिए विकसित करता हूं।

+0

मुझे नहीं पता कि आप किस माहौल में हैं, लेकिन Win32 में आप WaitForMultipleObjects का उपयोग कर कतार के बिना एक ही समय में सभी कतारों पर उपभोक्ता प्रतीक्षा कर सकते हैं। –

+1

मुझे खेद है, मैंने यह उल्लेख नहीं किया कि मैं मुख्य रूप से लिनक्स – ziu

+1

पर काम करता हूं, अगर आपको वास्तविक उत्तर नहीं मिलेगा, तो इसके साथ कई बफर सिंक करने की हवा होगी: http://neosmart.net/ ब्लॉग/2011/waitformultipleobjects-and-win32-events-for-linux-and-read-write-locks-for-windows/ –

उत्तर

2

मुझे लगता है कि मेरे पास वह है जो आप खोज रहे हैं। यह लॉक फ्री रिंग बफर कार्यान्वयन है जो उत्पादक/उपभोक्ता को अवरुद्ध करता है। आपको केवल परमाणु प्राइमेटिव तक पहुंच की आवश्यकता है - इस उदाहरण में मैं जीसीसी के sync फ़ंक्शंस का उपयोग करूंगा।

यह एक ज्ञात बग है - यदि आप 100% से अधिक बफर को ओवरफ़्लो करते हैं तो यह गारंटी नहीं दी जाती है कि कतार FIFO बनी हुई है (यह अभी भी उन्हें अंततः संसाधित करेगी)।

इस कार्यान्वयन पढ़ने पर निर्भर करता है/एक परमाणु आपरेशन (जो काफी संकेत के लिए गारंटी है) होने के रूप में बफर तत्वों लिख

struct ringBuffer 
{ 
    void** buffer; 
    uint64_t writePosition; 
    size_t size; 
    sem_t* semaphore; 
} 

//create the ring buffer 
struct ringBuffer* buf = calloc(1, sizeof(struct ringBuffer)); 
buf->buffer = calloc(bufferSize, sizeof(void*)); 
buf->size = bufferSize; 
buf->semaphore = malloc(sizeof(sem_t)); 
sem_init(buf->semaphore, 0, 0); 

//producer 
void addToBuffer(void* newValue, struct ringBuffer* buf) 
{ 
    uint64_t writepos = __sync_fetch_and_add(&buf->writePosition, 1) % buf->size; 

    //spin lock until buffer space available 
    while(!__sync_bool_compare_and_swap(&(buf->buffer[writePosition]), NULL, newValue)); 
    sem_post(buf->semaphore); 
}  

//consumer 
void processBuffer(struct ringBuffer* buf) 
{ 
    uint64_t readPos = 0; 
    while(1) 
    { 
     sem_wait(buf->semaphore); 

     //process buf->buffer[readPos % buf->size] 
     buf->buffer[readPos % buf->size] = NULL; 
     readPos++; 
    } 
} 
+1

यह उदाहरण दिलचस्प है। मुझे देखने दो कि क्या मैं अच्छी तरह से समझ गया हूं: इंडेक्स वृद्धि और लिखना लॉक-फ्री है, जबकि आप उपभोक्ता को अवरुद्ध करने के लिए एक सेमफोर फॉर्म में लॉक का उपयोग करते हैं, जब उपभोग करने के लिए कुछ भी नहीं है। मुझे समझ में नहीं आता कि यह बफर ओवरफ़्लो करना संभव है, हालांकि। इसके अलावा, क्या आपने इस संरचना का उपयोग ऐसे सिस्टम में किया था जहां आपको बहुत कम कताई का समय लगता था? कताई पाश का असर कैसा रहा? – ziu

+0

आप संसाधित किए जा सकने वाले डेटा से तेज़ी से लिखकर बफर को ओवरफ़्लो कर सकते हैं। अंत में लेखन सूचकांक चारों ओर आ जाएगा और पाठक को "पास" करेगा। इस बिंदु पर, लेखक को स्पिन लॉक में इंतजार करना पड़ता है, जबकि यह पाठक को पकड़ने की प्रतीक्षा करता है (अन्यथा यह बफर में डेटा ओवरराइट कर देगा)। बग तब होता है जब आप 100% से अधिक कतार को बहते हैं। इस परिदृश्य में, बफर के उपलब्ध होने के लिए आपके पास स्पिनलॉक में 1 से अधिक थ्रेड प्रतीक्षा कर रहे हैं। यह गारंटी नहीं है कि धागे कतार में पहले कौन से लिखेंगे। – charliehorse55

+2

निम्न में निम्नानुसार लूप को फिर से लिखना आसान नहीं होगा? 'जबकि (1) { जबकि (__ sync_bool_compare_and_swap (और (buf-> बफ़र [writePosition]), शून्य, newValue) == झूठी); sem_post (buf-> semaphore); ब्रेक; } ' – ziu

4

liblfds, एक ताला मुक्त MPMC ringbuffer देखें। यह सभी — लॉक-फ्री डेटा संरचनाओं को अवरुद्ध नहीं करेगा, ऐसा करने के लिए ऐसा नहीं होता है, क्योंकि लॉक-फ्री होने का बिंदु अवरोध से बचने के लिए है; आप को इसे संभालने की आवश्यकता है, जब डेटा संरचना NULLNULL पर वापस आती है, तो आप रिक्त पर पढ़ने का प्रयास करते हैं, लेकिन पूर्ण रूप से लिखते समय आपकी आवश्यकता से मेल नहीं खाते हैं; यहां, यह सबसे पुराना तत्व फेंक देगा और आपको अपने लेखन के लिए देगा।

हालांकि, यह केवल उस व्यवहार को प्राप्त करने के लिए एक छोटा संशोधन लेगा।

लेकिन एक बेहतर समाधान हो सकता है। एक रिंगबफर का मुश्किल हिस्सा तब होता है जब सबसे पुराने पिछले तत्व को पूरा किया जाता है और इसका पुनः उपयोग किया जाता है। आपको इसकी आवश्यकता नहीं है। मुझे लगता है कि आप एसपीएससी मेमोरी-बाrier केवल सर्कुलर बफर ले सकते हैं और परमाणु परिचालनों का उपयोग करके इसे फिर से लिख सकते हैं। यह लॉट अधिक प्रदर्शनकर्ता होगा जो MP12 रिंगबफर liblfds (जो कतार और ढेर का संयोजन है) में होगा।

+0

अब तक, मेरा एसपीएससी कार्यान्वयन काफी छोटा है: यह केवल स्थानीय और वैश्विक स्थिति काउंटर पर निर्भर करता है ताकि लेखक और पाठक को सिंक्रनाइज़ किया जा सके (स्थानीय काउंटर तत्वों के पुश/पुल को बैच करने और झूठी साझाकरण को कम करने के लिए हैं)। कताई को कम करने के लिए कंडीशन चर मौजूद हैं (यदि कोई डेटा उपलब्ध नहीं है तो ऐसा करने के लिए और कुछ नहीं है/यदि गंतव्य पूर्ण बैकप्रेसर अपरिहार्य है)। उचित स्मृति बाधाओं के बिना, मेरा कार्यान्वयन किसी अन्य वास्तुकला पर काम नहीं करेगा। क्या आप अपने अंतिम बिंदु के बारे में विस्तार से जानकारी दे सकते हैं? अंत में, रिंग बफर हमेशा एसपीएससी होगा, है ना? – ziu

+1

एक प्रसिद्ध एसपीएससी परिपत्र बफर है, उदाहरण के लिए लिनक्स कर्नेल में प्रयोग किया जाता है, जो केवल स्मृति बाधाओं का उपयोग करता है, बफर पूर्ण या खाली होने पर NULL लौटाता है।मुझे लगता है कि इसे परमाणु संचालन का उपयोग करके एमपीएमसी बनाया जा सकता है। –