मैं एक सार्वभौमिक निर्माण को और अधिक कुशल कैसे बना सकता हूं?


16

एक "सार्वभौमिक निर्माण" एक अनुक्रमिक वस्तु के लिए एक आवरण वर्ग है जो इसे रैखिक बनाने में सक्षम बनाता है (समवर्ती के लिए एक मजबूत स्थिरता की स्थिति)। उदाहरण के लिए, यहाँ जावा में एक अनुकूलित प्रतीक्षा-मुक्त निर्माण है, [1] से, जो एक प्रतीक्षा-मुक्त कतार के अस्तित्व को मानता है जो इंटरफ़ेस को संतुष्ट करता है WFQ(जिसमें केवल थ्रेड्स के बीच एक बार आम सहमति की आवश्यकता होती है) और एक Sequentialइंटरफ़ेस मानता है :

public interface WFQ<T> // "FIFO" iteration
{
    int enqueue(T t); // returns the sequence number of t
    Iterable<T> iterateUntil(int max); // iterates until sequence max
}
public interface Sequential
{
    // Apply an invocation (method + arguments)
    // and get a response (return value + state)
    Response apply(Invocation i); 
}
public interface Factory<T> { T generate(); } // generate new default object
public interface Universal extends Sequential {}

public class SlowUniversal implements Universal
{
    Factory<? extends Sequential> generator;
    WFQ<Invocation> wfq = new WFQ<Invocation>();
    Universal(Factory<? extends Sequential> g) { generator = g; } 
    public Response apply(Invocation i)
    {
        int max = wfq.enqueue(i);
        Sequential s = generator.generate();
        for(Invocation invoc : wfq.iterateUntil(max))
            s.apply(invoc);
        return s.apply(i);
    }
}

यह कार्यान्वयन बहुत संतोषजनक नहीं है क्योंकि यह वास्तव में धीमा है (आप हर आह्वान को याद करते हैं, और इसे हर आवेदन पर फिर से खेलना पड़ता है - हमारे पास इतिहास के आकार में रैखिक रनटाइम है)। क्या कोई तरीका है कि हम WFQऔर नए Sequentialइंटरफेस को लागू करते समय कुछ कदमों को बचाने के लिए सक्षम करने के लिए (उचित तरीकों से) इंटरफेस और बढ़ा सकते हैं ?

क्या हम प्रतीक्षा-मुक्त संपत्ति को खोए बिना इसे और अधिक कुशल बना सकते हैं (इतिहास के आकार में रेखीय रनटाइम नहीं, अधिमानतः स्मृति उपयोग भी कम हो जाता है)?

स्पष्टीकरण

एक "सार्वभौमिक निर्माण" एक ऐसा शब्द है जो मुझे पूरा यकीन है कि [1] द्वारा बनाया गया था, जो एक थ्रेड-असुरक्षित लेकिन थ्रेड-संगत ऑब्जेक्ट को स्वीकार करता है, जिसे Sequentialइंटरफ़ेस द्वारा सामान्यीकृत किया गया है। प्रतीक्षा-मुक्त कतार का उपयोग करते हुए, पहला निर्माण ऑब्जेक्ट के थ्रेड-सुरक्षित, रैखिक संस्करण प्रदान करता है जो कि प्रतीक्षा-मुक्त है (यह निर्धारण और applyसंचालन को रोक देता है)।

यह अक्षम है, क्योंकि विधि प्रभावी रूप से प्रत्येक स्थानीय थ्रेड को एक साफ स्लेट से शुरू करने के लिए है और उस पर दर्ज किए गए प्रत्येक ऑपरेशन को लागू करता है। किसी भी स्थिति में, यह काम करता है क्योंकि यह WFQउस क्रम को निर्धारित करने के लिए उपयोग करके प्रभावी ढंग से सिंक्रनाइज़ेशन को प्राप्त करता है जिसमें सभी ऑपरेशनों को लागू किया जाना चाहिए: प्रत्येक थ्रेड कॉलिंग applyमें समान स्थानीय Sequentialऑब्जेक्ट दिखाई देगा , उसी क्रम में Invocationएस लागू किया जाएगा।

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

शब्दजाल:

  1. प्रतीक्षा-मुक्त - थ्रेड्स की संख्या या शेड्यूलर के निर्णय की परवाह किए बिना, applyउस थ्रेड के लिए निष्पादित निर्देशों की एक सीमित रूप से बाध्य संख्या में समाप्त हो जाएगा।
  2. लॉक-फ़्री - ऊपर के समान, लेकिन एक निर्बाध निष्पादन समय की संभावना को स्वीकार करता है, केवल इस मामले में कि applyअन्य थ्रेड्स में एक अनबाउंड संचालन की संख्या हो रही है। आमतौर पर, आशावादी तुल्यकालन योजनाएं इस श्रेणी में आती हैं।
  3. अवरोधक - अनुसूचक की दया पर दक्षता।

अनुरोध के अनुसार एक कार्य उदाहरण (अब एक पृष्ठ पर जो समाप्त नहीं होगा)

[१] हेरली और शेविट, द आर्ट ऑफ़ मल्टीप्रोसेसर प्रोग्रामिंग


प्रश्न 1 केवल उत्तर देने योग्य है यदि हम जानते हैं कि "कार्य" आपके लिए क्या अर्थ है।
रॉबर्ट हार्वे

@RobertHarvey मैंने इसे सही किया - सभी को "काम" करने की आवश्यकता है रैपर को प्रतीक्षा-मुक्त होने के लिए और सभी ऑपरेशनों को CopyableSequentialवैध होने के लिए - रैखिकता को तब इस तथ्य से पालन करना चाहिए कि यह है Sequential
VF1

इस सवाल में बहुत सारे सार्थक शब्द हैं लेकिन मैं उन्हें समझने के लिए एक साथ रखने के लिए संघर्ष कर रहा हूं कि आप क्या हासिल करने की कोशिश कर रहे हैं। क्या आप इस बात की कुछ व्याख्या प्रदान कर सकते हैं कि आप किस समस्या को हल करने की कोशिश कर रहे हैं और शायद शब्दजाल को थोड़ा पतला कर सकते हैं?
जिमीजैमस

@JimmyJames मैं प्रश्न के अंदर एक "विस्तारित टिप्पणी" में विस्तृत है। कृपया मुझे बताएं कि क्या कोई अन्य शब्दजाल स्पष्ट करने के लिए है।
वीएफ 1

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

जवाबों:


1

यहाँ एक स्पष्टीकरण और उदाहरण दिया गया है कि यह कैसे पूरा होता है। मुझे बताएं कि क्या ऐसे हिस्से हैं जो स्पष्ट नहीं हैं।

स्रोत के साथ देते हैं

यूनिवर्सल

प्रारंभ:

थ्रेड इंडेक्स को एक परमाणु रूप से बढ़े हुए फैशन में लागू किया जाता है। यह एक AtomicIntegerनाम का उपयोग करके प्रबंधित किया जाता है nextIndex। इन अनुक्रमों को एक ThreadLocalउदाहरण के माध्यम से थ्रेड्स को सौंपा गया है जो कि अगले सूचकांक को प्राप्त करने nextIndexऔर इसे बढ़ाने के द्वारा खुद को प्रारंभ करता है । ऐसा पहली बार होता है जब प्रत्येक थ्रेड के सूचकांक को पहली बार प्राप्त किया जाता है। ThreadLocalइस धागे को बनाए गए अंतिम अनुक्रम को ट्रैक करने के लिए A बनाया गया है। यह आरंभिक 0. है। अनुक्रमिक फैक्टरी ऑब्जेक्ट संदर्भ में पारित और संग्रहीत है। AtomicReferenceArrayआकार के दो उदाहरण बनते हैं n। टेल ऑब्जेक्ट को प्रत्येक संदर्भ को सौंपा गया है, Sequentialकारखाने द्वारा प्रदान की गई प्रारंभिक अवस्था के साथ आरंभीकृत किया गया है । nअनुमति दी गई थ्रेड्स की अधिकतम संख्या है। इन सरणियों में प्रत्येक तत्व संबंधित थ्रेड इंडेक्स के अंतर्गत आता है।

लागू करने की विधि:

यह वह विधि है जो दिलचस्प काम करती है। यह निम्न कार्य करता है:

  • इस मंगलाचरण के लिए एक नया नोड बनाएँ: मेरा
  • वर्तमान थ्रेड्स इंडेक्स में ऐलान सरणी में इस नए नोड को सेट करें

फिर अनुक्रमण पाश शुरू होता है। यह तब तक जारी रहेगा जब तक कि वर्तमान आह्वान का अनुक्रम नहीं हो जाता:

  1. इस थ्रेड द्वारा बनाए गए अंतिम नोड के अनुक्रम का उपयोग करके ऐलान सरणी में एक नोड ढूंढें। इस पर और बाद में।
  2. यदि चरण 2 में एक नोड पाया जाता है, तो यह अभी तक अनुक्रमित नहीं है, इसके साथ जारी रखें, अन्यथा, बस वर्तमान आह्वान पर ध्यान दें। यह केवल एक दूसरे नोड प्रति आह्वान में मदद करने की कोशिश करेगा।
  3. चरण 3 में जो भी नोड चुना गया था, उसे अंतिम अनुक्रम नोड के बाद इसे अनुक्रमित करने की कोशिश करते रहें (अन्य धागे हस्तक्षेप कर सकते हैं।) सफलता के बावजूद, वर्तमान थ्रेड्स हेड संदर्भ को अनुक्रम द्वारा लौटाए गए सेट पर सेट करें। decideNext()

ऊपर वर्णित नेस्टेड लूप की कुंजी decideNext()विधि है। यह समझने के लिए, हमें नोड वर्ग को देखने की जरूरत है।

नोड वर्ग

यह वर्ग एक डबल-लिंक्ड सूची में नोड्स को निर्दिष्ट करता है। इस वर्ग में बहुत सारी कार्रवाई नहीं है। अधिकांश विधियां सरल पुनर्प्राप्ति विधियां हैं जो काफी आत्म-व्याख्यात्मक होनी चाहिए।

पूंछ विधि

यह 0. के अनुक्रम के साथ एक विशेष नोड उदाहरण देता है। यह बस एक स्थान धारक के रूप में कार्य करता है जब तक कि एक आह्वान इसे प्रतिस्थापित नहीं करता है।

गुण और प्रारंभ

  • seq: अनुक्रम संख्या, -1 से आरंभ की गई (जिसका अर्थ नहीं है)
  • invocation: के आह्वान का मूल्य apply()। निर्माण पर सेट करें।
  • next: AtomicReferenceफॉरवर्ड लिंक के लिए। एक बार असाइन करने के बाद, इसे कभी नहीं बदला जाएगा
  • previous: AtomicReferenceअनुक्रमण पर और मंजूरी दे दी पिछड़े लिंक के लिएtruncate()

आगे का फैसला

यह विधि गैर-तुच्छ तर्क के साथ नोड में केवल एक है। संक्षेप में, एक नोड को एक उम्मीदवार के रूप में लिंक किए गए सूची में अगले नोड के रूप में पेश किया जाता है। compareAndSet()अगर यह के संदर्भ रिक्त है विधि की जाँच करेगा और यदि ऐसा है तो, उम्मीदवार के संदर्भ में निर्धारित किया है। यदि संदर्भ पहले से ही सेट है, तो यह कुछ भी नहीं करता है। यह ऑपरेशन परमाणु है इसलिए यदि एक ही समय में दो उम्मीदवारों की पेशकश की जाती है, तो केवल एक का चयन किया जाएगा। यह गारंटी देता है कि केवल एक नोड को अगले एक के रूप में चुना जाएगा। यदि उम्मीदवार नोड चयनित है, तो यह अनुक्रम अगले मान पर सेट है, और यह पिछले लिंक इस नोड पर सेट है।

यूनिवर्सल क्लास में वापस कूदने की विधि लागू करें ...

decideNext()हमारे नोड या announceसरणी से नोड के साथ अंतिम अनुक्रमित नोड (जब चेक किया गया) पर कॉल किया जाता है , तो दो संभावित घटनाएं होती हैं: 1. नोड को सफलतापूर्वक अनुक्रमित किया गया था। कुछ अन्य धागे ने इस धागे को पूर्व-खाली किया।

अगला कदम यह जांचना है कि क्या इस मंगलाचरण के लिए नोड बनाया गया है। ऐसा इसलिए हो सकता है क्योंकि इस धागे ने सफलतापूर्वक इसे अनुक्रमित किया या किसी अन्य धागे ने इसे announceसरणी से उठाया और इसे हमारे लिए अनुक्रमित किया। यदि इसे अनुक्रमित नहीं किया गया है, तो प्रक्रिया को दोहराया जाता है। अन्यथा कॉल इस थ्रेड्स इंडेक्स के लिए ऐलान सरणी को हटाकर और आह्वान के परिणाम मान को वापस करके समाप्त होता है। घोषणा की सरणी को यह सुनिश्चित करने के लिए मंजूरी दे दी गई है कि नोड के आसपास कोई संदर्भ नहीं है जो नोड को कचरा एकत्र करने से रोकेगा और इसलिए उस बिंदु से लिंक सूची में सभी नोड्स को ढेर पर जिंदा रखेगा।

मूल्यांकन विधि

अब जब आह्वान के नोड को सफलतापूर्वक अनुक्रमित किया गया है, तो आह्वान का मूल्यांकन करने की आवश्यकता है। ऐसा करने के लिए, पहला कदम यह सुनिश्चित करना है कि इससे पहले हुए इनवोकेशन का मूल्यांकन किया गया है। यदि वे इस धागे का इंतजार नहीं करेंगे, लेकिन वह काम तुरंत कर देंगे।

सुनिश्चित विधि है

ensurePrior()विधि जुड़ा हुआ सूची में पिछले नोड की जाँच करके यह काम करता है। यदि यह स्थिति सेट नहीं है, तो पिछले नोड का मूल्यांकन किया जाएगा। नोड कि यह पुनरावर्ती है। यदि पूर्व नोड से पहले के नोड का मूल्यांकन नहीं किया गया है, तो यह उस नोड के लिए मूल्यांकन को आगे बढ़ाएगा।

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

MoveForward विधि

आगे की विधि इस नोड के सभी प्रमुख संदर्भों को स्थानांतरित करने का प्रयास करेगी यदि वे पहले से ही कुछ आगे की ओर इशारा नहीं कर रहे हैं। यह सुनिश्चित करने के लिए है कि यदि कोई थ्रेड कॉल करना बंद कर देता है, तो यह हेड एक नोड के संदर्भ को बनाए नहीं रखेगा, जिसकी अब आवश्यकता नहीं है। compareAndSet()विधि यकीन है कि हम केवल नोड अद्यतन करता है, तो कुछ अन्य धागा यह बाद से यह प्राप्त की गई थी नहीं बदला है कर देगा।

ऐरे और मदद की घोषणा करें

केवल लॉक-फ़्री के विपरीत इस दृष्टिकोण को प्रतीक्षा-मुक्त बनाने की कुंजी यह है कि हम यह नहीं मान सकते कि थ्रेड शेड्यूलर प्रत्येक थ्रेड को प्राथमिकता देगा जब उसे इसकी आवश्यकता होगी। यदि प्रत्येक धागे को केवल अनुक्रम करने का प्रयास किया जाता है, तो यह स्वयं नोड्स है, यह संभव है कि लोड के तहत एक धागा लगातार पूर्व-खाली किया जा सकता है। इस संभावना को ध्यान में रखते हुए, प्रत्येक थ्रेड पहले अन्य थ्रेड्स को 'मदद' करने का प्रयास करेगा, जो क्रमबद्ध होने में असमर्थ हो सकते हैं।

मूल विचार यह है कि जैसा कि प्रत्येक थ्रेड सफलतापूर्वक नोड बनाता है, असाइन किए गए अनुक्रम मोनोटोनिक रूप से बढ़ रहे हैं। यदि एक थ्रेड या थ्रेड लगातार दूसरे थ्रेड को खाली कर रहे हैं, तो इंडेक्स को announceसरणी में अनियोजित नोड्स खोजने के लिए उपयोग आगे बढ़ेगा। यहां तक ​​कि अगर प्रत्येक थ्रेड जो वर्तमान में दिए गए नोड को अनुक्रमित करने की कोशिश कर रहा है, तो लगातार दूसरे धागे द्वारा पूर्व-खाली किया जाता है, अंततः सभी थ्रेड्स उस नोड को अनुक्रमित करने की कोशिश करेंगे। उदाहरण के लिए, हम तीन थ्रेड्स के साथ एक उदाहरण का निर्माण करेंगे।

प्रारंभिक बिंदु पर, सभी तीन थ्रेड्स सिर और घोषणा तत्व tailनोड पर इंगित किए जाते हैं । lastSequenceप्रत्येक थ्रेड के लिए 0 है।

इस बिंदु पर, थ्रेड 1 को एक आह्वान के साथ निष्पादित किया जाता है। यह अंतिम अनुक्रम (शून्य) के लिए घोषणा सरणी की जांच करता है जो कि नोड है जिसे वर्तमान में सूचकांक में निर्धारित किया गया है। यह नोड को अनुक्रमित करता है और इसे lastSequence1 पर सेट किया जाता है।

थ्रेड 2 को अब एक आह्वान के साथ निष्पादित किया जाता है, यह अंतिम अनुक्रम (शून्य) पर ऐलान सरणी की जांच करता है और देखता है कि इसे मदद की आवश्यकता नहीं है और इसलिए यह आह्वान करने के लिए अनुक्रम का प्रयास करता है। यह सफल होता है और अब यह lastSequence2 पर सेट है।

थ्रेड 3 को अब निष्पादित किया गया है और यह भी देखता है कि नोड announce[0]पहले से ही अनुक्रमित है और अनुक्रमों में यह स्वयं का आह्वान है। यह lastSequenceअब 3 पर सेट है।

अब थ्रेड 1 को फिर से लाया जाता है। यह इंडेक्स 1 में घोषणा सरणी की जांच करता है और पाता है कि यह पहले से ही अनुक्रमित है। समवर्ती, थ्रेड 2 को लागू किया जाता है। यह इंडेक्स 2 में घोषणा सरणी की जांच करता है और पाता है कि यह पहले से ही अनुक्रमित है। दोनों थ्रेड 1 और थ्रेड 2 अब अपने स्वयं के नोड्स क्रम करने का प्रयास। थ्रेड 2 जीतता है और यह अनुक्रम है। यह lastSequence4 पर सेट है। इस बीच, थ्रेड तीन को लागू किया गया है। यह सूचकांक को जांचता है lastSequence(मॉड 3) और पाता है कि नोड का announce[0]अनुक्रम नहीं किया गया है। थ्रेड 2 को फिर से उसी समय लागू किया जाता है जब थ्रेड 1 चालू होता है। धागा 1एक अप्रयुक्त आह्वान पाता है announce[1]जिस पर केवल थ्रेड 2 द्वारा निर्मित नोड है । यह थ्रेड 2 के आह्वान को अनुक्रम करने का प्रयास करता है और सफल होता है। थ्रेड 2 पाता है कि यह स्वयं का नोड है announce[1]और इसे अनुक्रमित किया गया है। यह 5 सेट lastSequenceहै। थ्रेड 3 को फिर से लागू किया जाता है और पाया जाता है कि 1 पर रखा गया नोड announce[0]अभी भी अनुक्रमित नहीं है और ऐसा करने का प्रयास करता है। इस बीच थ्रेड 2 को भी शामिल किया गया है और थ्री-प्री-थ्रेड्स थ्री। यह अनुक्रम नोड है और इसे lastSequence6 पर सेट करता है ।

खराब धागा 1 । यद्यपि थ्रेड 3 इसे अनुक्रमित करने की कोशिश कर रहा है, लेकिन दोनों थ्रेड्स को लगातार अनुसूचक द्वारा विफल किया गया है। लेकिन इस बिंदु पर। थ्रेड 2 भी अब announce[0](6 मॉड 3) की ओर इशारा कर रहा है । सभी तीन धागे एक ही आह्वान का अनुक्रम करने का प्रयास करते हैं। कोई बात नहीं जो धागा सफल होता है, अगले नोड को अनुक्रमित किया जाएगा थ्रेड 1 का प्रतीक्षा मंगलाचरण यानी नोड द्वारा संदर्भित announce[0]

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


क्या आप pastebin पर कुछ कोड अंश डालेंगे? बहुत सी चीजें (जैसे लॉकफ्री लिंक्ड लिस्ट) को बस ऐसे ही कहा जा सकता है? जब बहुत सारे विवरण हों तो आपके उत्तर को एक पूरे के रूप में समझना थोड़ा मुश्किल है। किसी भी मामले में, यह आशाजनक लग रहा है, मैं निश्चित रूप से यह सुनिश्चित करने के लिए खोदना चाहूंगा कि यह क्या प्रदान करता है।
वीएफ 1

यह निश्चित रूप से एक वैध लॉक-फ्री कार्यान्वयन की तरह लगता है, लेकिन यह मूल मुद्दे को याद कर रहा है जिसके बारे में मैं चिंतित हूं। रैखिकता की आवश्यकता को "वैध इतिहास" प्रस्तुत करने की आवश्यकता होती है, जो लिंक-लिस्ट कार्यान्वयन के मामले में, वैध होने के लिए एक previousऔर nextसूचक की आवश्यकता होती है। प्रतीक्षा-मुक्त तरीके से एक मान्य इतिहास को बनाए रखना और बनाना कठिन लगता है।
VF1

@ VF1 मुझे यकीन नहीं है कि क्या मुद्दा संबोधित नहीं है। बाकी टिप्पणी में आप जो कुछ भी उल्लेख करते हैं, वह मेरे द्वारा बताए गए उदाहरण से संबोधित किया जाता है।
जिमीजैम्स

आपने प्रतीक्षा-मुक्त संपत्ति छोड़ दी है ।
VF1

@ VF1 आप कैसे आंकते हैं?
जिम्मीजम्स

0

मेरा पिछला उत्तर वास्तव में प्रश्न का ठीक से उत्तर नहीं देता है, लेकिन जैसे ही ओपी इसे उपयोगी देखता है, मैं इसे छोड़ दूंगा। प्रश्न में लिंक में कोड के आधार पर, यहां मेरा प्रयास है। मैंने इस पर केवल मूल परीक्षण किया है, लेकिन यह ठीक से औसत की गणना करता प्रतीत होता है। फीडबैक का स्वागत किया गया कि क्या यह ठीक से प्रतीक्षा-मुक्त है।

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

अनुक्रमिक और फैक्टरी

public interface Sequential<E, S, R>
{ 
  R apply(S priorState);

  S state();

  default boolean isApplied()
  {
    return state() != null;
  }
}

public interface Factory<E, S, R>
{
   S initial();

   Sequential<E, S, R> generate(E input);
}

यूनिवर्सल

import java.util.concurrent.ConcurrentLinkedQueue;

public class Universal<I, S, R> 
{
  private final Factory<I, S, R> generator;
  private final ConcurrentLinkedQueue<Sequential<I, S, R>> wfq = new ConcurrentLinkedQueue<>();
  private final ThreadLocal<Sequential<I, S, R>> last = new ThreadLocal<>();

  public Universal(Factory<I, S, R> g)
  { 
    generator = g;
  }

  public R apply(I invocation)
  {
    Sequential<I, S, R> newSequential = generator.generate(invocation);
    wfq.add(newSequential);

    Sequential<I, S, R> last = null;
    S prior = generator.initial(); 

    for (Sequential<I, S, R> i : wfq) {
      if (!i.isApplied() || newSequential == i) {
        R r = i.apply(prior);

        if (i == newSequential) {
          wfq.remove(last.get());
          last.set(newSequential);

          return r;
        }
      }

      prior = i.state();
    }

    throw new IllegalStateException("Houston, we have a problem");
  }
}

औसत

public class Average implements Sequential<Integer, Average.State, Double>
{
  private final Integer invocation;
  private volatile State state;

  private Average(Integer invocation)
  {
    this.invocation = invocation;
  }

  @Override
  public Double apply(State prior)
  {
    System.out.println(Thread.currentThread() + " " + invocation + " prior " + prior);

    state = prior.add(invocation);

    return ((double) state.sum)/ state.count;
  }

  @Override
  public State state()
  {
    return state;
  }

  public static class AverageFactory implements Factory<Integer, State, Double> 
  {
    @Override
    public State initial()
    {
      return new State(0, 0);
    }

    @Override
    public Average generate(Integer i)
    {
      return new Average(i);
    }
  }

  public static class State
  {
    private final int sum;
    private final int count;

    private State(int sum, int count)
    {
      this.sum = sum;
      this.count = count;
    }

    State add(int value)
    {
      return new State(sum + value, count + 1);
    }

    @Override
    public String toString()
    {
      return sum + " / " + count;
    }
  }
}

डेमो कोड

private static final int THREADS = 10;
private static final int SIZE = 50;

public static void main(String... args)
{
  Average.AverageFactory factory = new Average.AverageFactory();

  Universal<Integer, Average.State, Double> universal = new Universal<>(factory);

  for (int i = 0; i < THREADS; i++)
  {
    new Thread(new Test(i * SIZE, universal)).start();
  }
}

static class Test implements Runnable
{
  final int start;
  final Universal<Integer, Average.State, Double> universal;

  Test(int start, Universal<Integer, Average.State, Double> universal)
  {
    this.start = start;
    this.universal = universal;
  }

  @Override
  public void run()
  {
    for (int i = start; i < start + SIZE; i++)
    {
      System.out.println(Thread.currentThread() + " " + i);

      System.out.println(System.nanoTime() + " " + Thread.currentThread() + " " + i + " result " + universal.apply(i));
    }
  }
}

मैंने कोड को कुछ संपादन किया क्योंकि मैं इसे यहाँ पोस्ट कर रहा था। यह ठीक होना चाहिए, लेकिन मुझे बताएं कि क्या आपके पास इसके मुद्दे हैं।


आपको अपना अन्य उत्तर मेरे लिए नहीं रखना है (मैंने पहले से अपने प्रश्न को अपडेट किया है ताकि इससे कोई प्रासंगिक निष्कर्ष निकाला जा सके)। दुर्भाग्य से, यह उत्तर प्रश्न का उत्तर नहीं देता है, क्योंकि यह वास्तव में किसी भी मेमोरी को मुक्त नहीं करता है wfq, इसलिए आपको अभी भी पूरे इतिहास से गुजरना होगा - रनटाइम में एक स्थिर कारक को छोड़कर सुधार नहीं हुआ है।
VF1

@ Vf1 समय यह पूरी सूची के माध्यम से पता लगाने के लिए कि क्या यह गणना की गई है कि प्रत्येक गणना करने की तुलना में miniscule होने जा रहा है। क्योंकि पिछले राज्यों की आवश्यकता नहीं है, इसलिए प्रारंभिक राज्यों को हटाना संभव होना चाहिए। परीक्षण कठिन है और इसके लिए अनुकूलित संग्रह का उपयोग करने की आवश्यकता हो सकती है, लेकिन मैंने एक छोटा परिवर्तन जोड़ा है।
जिमीजैम

@ VF1 एक कार्यान्वयन के लिए अद्यतन किया गया है जो मूल सरसरी परीक्षण के साथ काम करता है। मुझे यकीन नहीं है कि यह सुरक्षित है, लेकिन मेरे सिर के ऊपर से, अगर सार्वभौमिक को उन थ्रेड्स के बारे में पता था जो इसके साथ काम कर रहे हैं, तो यह प्रत्येक थ्रेड का ट्रैक रख सकता है और तत्वों को हटा सकता है, जब सभी थ्रेड्स सुरक्षित रूप से पिछले हो जाते हैं।
जिमीजैम 15

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

"प्रारंभिक राज्यों को निकालना संभव होना चाहिए" - बिल्कुल। यह होना चाहिए , लेकिन इसके कोड को आसानी से पेश करना आसान है जो प्रतीक्षा स्वतंत्रता खो देता है। थ्रेड-ट्रैकिंग योजना काम कर सकती है। अंत में, मेरे पास सीएलक्यू स्रोत तक पहुंच नहीं है, क्या आप लिंक करना चाहेंगे?
VF1
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.