स्मृति प्रबंधित भाषाओं के लिए एक संदर्भ गिनती पैटर्न?


11

जावा और .NET के पास अद्भुत कचरा संग्रहकर्ता हैं जो आपके लिए मेमोरी का प्रबंधन करते हैं, और बाहरी वस्तुओं ( Closeable, IDisposable) को जल्दी से जारी करने के लिए सुविधाजनक पैटर्न हैं , लेकिन केवल अगर वे एक ही वस्तु के स्वामित्व में हैं। कुछ प्रणालियों में एक संसाधन को दो घटकों द्वारा स्वतंत्र रूप से उपभोग करने की आवश्यकता हो सकती है, और केवल तब जारी किया जाता है जब दोनों घटक संसाधन जारी करते हैं।

आधुनिक C ++ में आप इस समस्या को हल करेंगे shared_ptr, जो सभी के shared_ptrनष्ट होने पर संसाधन को निर्दिष्‍ट रूप से जारी करेगा ।

क्या महंगे संसाधनों को प्रबंधित करने और जारी करने के लिए कोई प्रलेखित, सिद्ध पैटर्न हैं जो ऑब्जेक्ट ओरिएंटेड, गैर-नियतात्मक रूप से कचरा एकत्र किए गए सिस्टम में एक भी मालिक नहीं हैं?



1
@JoshCaswell हाँ, और यह समस्या को हल करेगा, लेकिन मैं एक कचरा एकत्र स्थान में काम कर रहा हूं।
सी। रॉस

8
संदर्भ गिनती है एक कूड़ा संग्रह रणनीति।
जॉर्ज डब्ल्यू मित्तग

जवाबों:


15

सामान्य तौर पर, आप एकल मालिक होने से बचते हैं - यहां तक ​​कि अप्रबंधित भाषाओं में भी।

लेकिन सिद्धांत प्रबंधित भाषाओं के लिए समान है। तुरंत एक पर महंगा संसाधन बंद करने के बजाय Close()आप एक काउंटर घटती (पर वृद्धि Open()/ Connect()/ आदि) जब तक आप 0, जिसके आधार पर करीब वास्तव में पास करता है मारा। यह संभवतः फ्लाईवेट पैटर्न की तरह दिखेगा और कार्य करेगा।


यह वही है जो मैं भी सोच रहा था, लेकिन क्या इसके लिए एक प्रलेखित पैटर्न है? फ्लाईवेट निश्चित रूप से समान है, लेकिन विशेष रूप से स्मृति के लिए जैसा कि आमतौर पर परिभाषित किया गया है।
सी। रॉस

@ C.Ross यह ऐसा मामला प्रतीत होता है जिसमें अंतिम रूप देने वालों को प्रोत्साहित किया जाता है। आप संसाधन जारी करने के लिए उस वर्ग के लिए एक अंतिम उपकरण जोड़कर, अप्रबंधित संसाधन के चारों ओर एक आवरण वर्ग का उपयोग कर सकते हैं। आप इसे लागू भी कर सकते हैं IDisposable, जितनी जल्दी हो सके संसाधन जारी करने के लिए मायने रखता है, आदि। सबसे अच्छी बात है, बहुत बार, तीनों के लिए है, लेकिन अंतिम रूप से सबसे महत्वपूर्ण हिस्सा है, और IDisposableकार्यान्वयन है सबसे कम महत्वपूर्ण।
पैंज़रक्रिसिस

11
@Panzercrisis को छोड़कर, फाइनल चलाने के लिए गारंटी नहीं है, और विशेष रूप से तुरंत चलाने की गारंटी नहीं है ।
काल डेथ

@ कैलेथ मैं सोच रहा था कि गिनती की बात मुस्तैदी से मदद करेगी। जहां तक ​​वे बिल्कुल नहीं चल रहे हैं, तो क्या आपका मतलब है कि कार्यक्रम समाप्त होने से पहले सीएलआर बस इसके आसपास नहीं पहुंच सकता है, या क्या आप इसका मतलब है कि वे एकमुश्त अयोग्य हो सकते हैं?
Panzercrisis


14

एक कचरा एकत्र की गई भाषा में (जहां जीसी निर्धारक नहीं है), किसी वस्तु के जीवनकाल के लिए स्मृति के अलावा किसी अन्य संसाधन की सफाई को मज़बूती से बाँधना संभव नहीं है: किसी वस्तु को हटा दिया जाना संभव नहीं है। जीवनकाल का अंत पूरी तरह से कचरा कलेक्टर के विवेक पर है। जीसी केवल इस बात की गारंटी देता है कि कोई वस्तु उपलब्ध होगी, जबकि वह उपलब्ध होगी। एक बार जब कोई वस्तु अप्राप्य हो जाती है, तो उसे भविष्य में किसी बिंदु पर साफ किया जा सकता है, जिसमें अंतिम रूप से चलने वाले उपकरण शामिल हो सकते हैं।

"संसाधन स्वामित्व" की अवधारणा वास्तव में GC भाषा में लागू नहीं होती है। जीसी प्रणाली सभी वस्तुओं का मालिक है।

इन भाषाओं में प्रयास-साथ-संसाधन + क्लोजेबल (जावा) के साथ, स्टेटमेंट्स + आईडीसॉर्पोरेटरी (C #) का उपयोग करके, या स्टेटमेंट्स + संदर्भ प्रबंधकों (पायथन) के साथ रिसोर्स फ़्लो (! = ऑब्जेक्ट्स) के लिए एक संसाधन रखने का एक तरीका है। बंद हो जाता है जब नियंत्रण प्रवाह एक गुंजाइश छोड़ देता है। इन सभी मामलों में, यह स्वचालित रूप से सम्मिलित के समान है try { ... } finally { resource.close(); }। संसाधन का प्रतिनिधित्व करने वाली वस्तु का जीवनकाल संसाधन के जीवनकाल से संबंधित नहीं है: संसाधन के बंद होने के बाद भी वस्तु का जीना जारी रह सकता है, और संसाधन के खुला रहने पर वस्तु अनुपलब्ध हो सकती है।

स्थानीय चर के मामले में, ये दृष्टिकोण RAII के बराबर हैं, लेकिन कॉल साइट पर स्पष्ट रूप से उपयोग करने की आवश्यकता है (C ++ विध्वंसक के विपरीत जो डिफ़ॉल्ट रूप से चलेगा)। एक अच्छा आईडीई चेतावनी दी जाएगी जब यह छोड़ दिया जाता है।

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

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

यह एकमात्र व्यवहार्य समाधान के रूप में निकलता है: मैन्युअल संसाधन प्रबंधन, संभवतः संदर्भ की गणना को स्वयं लागू करके। यह त्रुटि-प्रवण है, लेकिन असंभव नहीं है। विशेष रूप से, जीसी भाषाओं में स्वामित्व के बारे में सोचना असामान्य है, इसलिए स्वामित्व गारंटी के बारे में मौजूदा कोड पर्याप्त रूप से स्पष्ट नहीं हो सकता है।


3

अन्य उत्तरों से बहुत अच्छी जानकारी।

फिर भी, स्पष्ट होने के लिए, जिस पैटर्न की आप तलाश कर रहे हैं, वह यह है कि आप RAII जैसे नियंत्रण प्रवाह निर्माण के लिए छोटे एकल स्वामित्व वाली वस्तुओं का उपयोग करते हैं usingऔर IDispose, (बड़ी, संभवत: संदर्भित गणना की गई) वस्तु के साथ मिलकर जो कुछ (संचालन) रखता है प्रणाली) संसाधन।

इसलिए छोटी अनसोल्ड सिंगल ओनर ऑब्जेक्ट्स (छोटी वस्तु के माध्यम से IDisposeऔर usingकंट्रोल फ्लो कंस्ट्रक्शन के माध्यम से) बड़ी साझा ऑब्जेक्ट (शायद कस्टम Acquireऔर Releaseविधियों) को सूचित कर सकते हैं ।

( नीचे दिखाए गए तरीके Acquireऔर Releaseप्रयोग भी निर्माण के बाहर उपलब्ध हैं, लेकिन tryनिहित की सुरक्षा के बिना using।)


C # में एक उदाहरण

void Test ( MyRefCountedClass myObj )
{
    using ( var usingRef = myObj.Acquire () )
    {
        var item = usingRef.Item;
        item.SomeMethod ();

        // the `using` automatically invokes Dispose() on usingRef
        //  which in turn invokes Release() on `myObj.
    }
}

interface IReferencable<T> where T: IReferencable<T> {
    Reference<T> Acquire ();
    void Release();
}

struct Reference<T>: IDisposable where T: IReferencable<T>
{
    public readonly T Item;
    public Reference(T item) { Item = item; _released = false; }
    public void Dispose() { if (! _released ) { _released = true; Item.Release(); } }
    private bool _released;
}

class MyRefCountedClass : IReferencable<MyRefCountedClass>
{
    private int _refCount = 0;

    public Reference<MyRefCountedClass> Acquire ()
    {
        _refCount++;
        return new Reference<MyRefCountedClass>(this);
    }

    public void Release ()
    {
        if (--_refCount <= 0)
            Dispose();
    }

    // NOTE that MyRefCountedClass does not have to implement IDisposable, but it can...
    // as shown here it doesn't implement the interface
    private void Dispose ()  
    {
        if ( _refCount > 0 )
            throw new Exception ("Dispose attempted on item in use.");
        // release other resources...
    }

    public int SomeMethod()
    {
        return 0;
    }
}

यदि वह C # होना चाहिए (जो ऐसा दिखता है) तो आपका संदर्भ <T> कार्यान्वयन सूक्ष्म रूप से गलत है। एक ही वस्तु पर कई बार IDisposable.Disposeकॉल Disposeकरने वाले राज्यों के लिए अनुबंध एक नो-ऑप होना चाहिए। अगर मैं इस तरह के पैटर्न को लागू करने के लिए था, तो मैं Releaseअनावश्यक त्रुटियों से बचने और विरासत के बजाय प्रतिनिधिमंडल का उपयोग करने के लिए निजी SharedDisposableबनाऊंगा।
वू

@Voo, ओके, गुड पॉइंट, thx!
एरिक Eidt

1

एक प्रणाली में वस्तुओं का विशाल बहुमत आमतौर पर तीन पैटर्न में से एक को फिट करना चाहिए:

  1. जिन वस्तुओं का राज्य कभी नहीं बदलेगा, और जिन संदर्भों को विशुद्ध रूप से राज्य को घेरने के साधन के रूप में रखा गया है। ऐसी प्रविष्टियाँ जो संदर्भों को न तो जानती हैं और न ही इस बात की परवाह करती हैं कि क्या कोई अन्य संस्थाएँ उसी वस्तु के संदर्भ रखती हैं।

  2. वे वस्तुएँ जो किसी एकल इकाई के अनन्य नियंत्रण में होती हैं, जो सभी राज्य का एकमात्र स्वामी होता है, और वस्तु को शुद्ध रूप से (संभवतः उत्परिवर्तित) राज्य में संलग्न करने के साधन के रूप में उपयोग करता है।

  3. जिन वस्तुओं का स्वामित्व एकल इकाई के पास है, लेकिन अन्य संस्थाओं को सीमित तरीके से उपयोग करने की अनुमति है। ऑब्जेक्ट का मालिक न केवल इसे एनकैप्सुलेटिंग स्थिति के साधन के रूप में उपयोग कर सकता है, बल्कि अन्य संस्थाओं के साथ एक रिश्ते को इनकैप्सुलेट कर सकता है।

ट्रैकिंग कचरा-संग्रह # 1 के लिए संदर्भ गिनती से बेहतर काम करता है, क्योंकि ऐसी वस्तुओं का उपयोग करने वाले कोड को अंतिम शेष संदर्भ के साथ किए जाने पर कुछ विशेष करने की आवश्यकता नहीं होती है। # 2 के लिए संदर्भ-गणना की आवश्यकता नहीं है, क्योंकि ऑब्जेक्ट में एक ही मालिक होगा, और यह तब पता चलेगा जब उसे ऑब्जेक्ट की आवश्यकता नहीं होगी। परिदृश्य # 3 में कुछ कठिनाई हो सकती है यदि किसी वस्तु का मालिक उसे मारता है जबकि अन्य संस्थाएं अभी भी संदर्भ रखती हैं; यहां तक ​​कि, ट्रैकिंग ट्रैकिंग GC यह सुनिश्चित करने के संदर्भ में गिनती से बेहतर हो सकता है कि मृत वस्तुओं के संदर्भ, मृत वस्तुओं के संदर्भ में विश्वसनीय रूप से पहचाने जा सकते हैं, जब तक कि इस तरह के कोई भी संदर्भ मौजूद हैं।

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


0

साझा स्वामित्व दुर्लभता नब्ज बनाता है

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

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

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

संसाधन लीक

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

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

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

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

समाधान: आस्थगित, आवधिक निष्कासन

इसलिए मेरा समाधान बाद में जिस पर मैंने अपनी व्यक्तिगत परियोजनाओं पर लागू किया, जिसने मुझे दोनों दुनियाओं से मुझे मिला सबसे अच्छा था कि इस अवधारणा को खत्म करना था, referencing=ownershipलेकिन अभी भी संसाधनों का विनाश टाल दिया है।

नतीजतन, अब जब भी उपयोगकर्ता कुछ ऐसा करता है जिसके कारण संसाधन को हटाने की आवश्यकता होती है, तो संसाधन को हटाने के संदर्भ में एपीआई व्यक्त किया जाता है:

ecs->remove(component);

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

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

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

एक जगह जहां मुझे रिफ-काउंटिंग या जीसी उपयोगी पाया गया, वह है लगातार डेटा स्ट्रक्चर्स के लिए। उस मामले में यह डेटा संरचना क्षेत्र है, जो उपयोगकर्ता-अंत की चिंताओं से दूर है, और यह वास्तव में प्रत्येक अपरिवर्तनीय प्रतिलिपि के लिए समान अनमॉडिफाइड डेटा के स्वामित्व को साझा करने के लिए समझ में आता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.