C # ऑब्जेक्ट पूलिंग पैटर्न कार्यान्वयन


165

किसी के पास Sql कनेक्शन पूलिंग की नस में एक सीमित संसाधन के लिए साझा ऑब्जेक्ट पूल रणनीति को लागू करने का एक अच्छा संसाधन है? (यानी पूरी तरह से लागू किया जाएगा कि यह धागा सुरक्षित है)।

@ स्पष्टीकरण के लिए अनुरोध के संबंध में पालन करने के लिए पूल का उपयोग किसी बाहरी सेवा के लिए लोड संतुलन अनुरोधों के लिए होगा। इसे ऐसे परिदृश्य में रखना जो शायद मेरे सीधे बैठने के विरोध के रूप में तुरंत समझने में आसान होगा। मेरे पास एक सत्र ऑब्जेक्ट है जो ISessionNHibernate से ऑब्जेक्ट के समान कार्य करता है । यह प्रत्येक अनूठे सत्र को डेटाबेस से जोड़ता है। वर्तमान में मेरे पास 1 लंबे समय तक चलने वाला सत्र ऑब्जेक्ट है और उन मुद्दों का सामना कर रहा हूं जहां मेरे सेवा प्रदाता इस व्यक्तिगत सत्र के मेरे उपयोग को सीमित कर रहे हैं।

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

उम्मीद है कि पृष्ठभूमि कुछ मूल्य प्रदान करती है लेकिन आपके कुछ सवालों के सीधे जवाब देने के लिए:

प्रश्न: क्या वस्तुएं बनाना महंगा है?
A: कोई ऑब्जेक्ट सीमित संसाधनों का एक पूल नहीं है

प्रश्न: क्या वे बहुत बार अधिग्रहित / जारी किए जाएंगे?
A: हाँ, एक बार फिर से उन्हें NHibernate ISession के बारे में सोचा जा सकता है जहां 1 को आमतौर पर अधिग्रहित किया जाता है और हर एक पृष्ठ अनुरोध की अवधि के लिए जारी किया जाता है।

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

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

प्रश्न: प्राथमिकताओं, आलसी बनाम उत्सुक लोडिंग, आदि जैसी चीजों के बारे में क्या?
A: इसमें कोई प्राथमिकता शामिल नहीं है, सादगी के लिए केवल यह मान लें कि मैं स्वयं पूल के निर्माण में उपलब्ध वस्तुओं का पूल बनाऊंगा।


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

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

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

1
मुझे लगता है कि टीपीएल डेटाफ्लो बफ़रब्लॉक आपको सबसे ज़्यादा ज़रूरत है।
खर्चा

1
थ्रेडेड वातावरण में पूलिंग एक आवर्ती समस्या है, जिसे संसाधन पूल और संसाधन कैश जैसे डिज़ाइन पैटर्न द्वारा हल किया जाता है। की जाँच करें पैटर्न उन्मुख सॉफ्टवेयर वास्तुकला, खंड 3: संसाधन प्रबंधन के लिए पैटर्न अधिक जानकारी के लिए।
फुरमान्टर

जवाबों:


59

.NET कोर में ऑब्जेक्ट पूलिंग

डॉटनैट कोर वस्तु पूलिंग आधार वर्ग पुस्तकालय (बीसीएल) को जोड़ा गया के एक कार्यान्वयन है। आप मूल GitHub मुद्दे को यहाँ पढ़ सकते हैं और System.Buffers के लिए कोड देख सकते हैं । वर्तमान में ArrayPoolएकमात्र प्रकार उपलब्ध है और इसका उपयोग पूल सरणियों के लिए किया जाता है। यहाँ पर एक अच्छी ब्लॉग पोस्ट है

namespace System.Buffers
{
    public abstract class ArrayPool<T>
    {
        public static ArrayPool<T> Shared { get; internal set; }

        public static ArrayPool<T> Create(int maxBufferSize = <number>, int numberOfBuffers = <number>);

        public T[] Rent(int size);

        public T[] Enlarge(T[] buffer, int newSize, bool clearBuffer = false);

        public void Return(T[] buffer, bool clearBuffer = false);
    }
}

इसके उपयोग का एक उदाहरण ASP.NET Core में देखा जा सकता है। क्योंकि यह डॉटनेट कोर बीसीएल में है, ASP.NET Core इसे अन्य ऑब्जेक्ट्स जैसे कि Newtonsoft.Json के JSON सीरियलाइज़र के साथ ऑब्जेक्ट पूल साझा कर सकता है। आप इस ब्लॉग पोस्ट को Newtonsoft.Json के बारे में अधिक जानकारी के लिए पढ़ सकते हैं ।

Microsoft Roslyn C # Compiler में ऑब्जेक्ट पूलिंग

नए Microsoft Roslyn C # कंपाइलर में ObjectPool प्रकार होता है, जिसका उपयोग अक्सर उपयोग की जाने वाली वस्तुओं को पूल करने के लिए किया जाता है जो आमतौर पर new'ed अप और कचरा अक्सर एकत्र होते हैं। यह कचरा संग्रहण कार्यों की मात्रा और आकार को कम करता है जो कि घटित होते हैं। ObjectPool का उपयोग करते हुए कुछ अलग उप-कार्यान्वयन हैं (देखें: रोज़लिन में ऑब्जेक्ट पूल के इतने सारे कार्यान्वयन क्यों हैं? )।

1 - SharedPools - यदि BigDefault का उपयोग किया जाता है, तो 20 ऑब्जेक्ट या 100 का एक पूल स्टोर करता है।

// Example 1 - In a using statement, so the object gets freed at the end.
using (PooledObject<Foo> pooledObject = SharedPools.Default<List<Foo>>().GetPooledObject())
{
    // Do something with pooledObject.Object
}

// Example 2 - No using statement so you need to be sure no exceptions are not thrown.
List<Foo> list = SharedPools.Default<List<Foo>>().AllocateAndClear();
// Do something with list
SharedPools.Default<List<Foo>>().Free(list);

// Example 3 - I have also seen this variation of the above pattern, which ends up the same as Example 1, except Example 1 seems to create a new instance of the IDisposable [PooledObject<T>][4] object. This is probably the preferred option if you want fewer GC's.
List<Foo> list = SharedPools.Default<List<Foo>>().AllocateAndClear();
try
{
    // Do something with list
}
finally
{
    SharedPools.Default<List<Foo>>().Free(list);
}

2 - ListPool और StringBuilderPool - सख्ती से अलग कार्यान्वयन नहीं है लेकिन विशेष रूप से सूची और StringBuilder के लिए ऊपर दिखाए गए SharedPools कार्यान्वयन के आसपास रैपर। तो यह SharedPools में संग्रहीत वस्तुओं के पूल का फिर से उपयोग करता है।

// Example 1 - No using statement so you need to be sure no exceptions are thrown.
StringBuilder stringBuilder= StringBuilderPool.Allocate();
// Do something with stringBuilder
StringBuilderPool.Free(stringBuilder);

// Example 2 - Safer version of Example 1.
StringBuilder stringBuilder= StringBuilderPool.Allocate();
try
{
    // Do something with stringBuilder
}
finally
{
    StringBuilderPool.Free(stringBuilder);
}

3 - पूलडेड और पूलडशैशसेट - ये सीधे ऑब्जेक्टपूल का उपयोग करते हैं और वस्तुओं का पूरी तरह से अलग पूल होते हैं। 128 वस्तुओं का एक पूल स्टोर करता है।

// Example 1
PooledHashSet<Foo> hashSet = PooledHashSet<Foo>.GetInstance()
// Do something with hashSet.
hashSet.Free();

// Example 2 - Safer version of Example 1.
PooledHashSet<Foo> hashSet = PooledHashSet<Foo>.GetInstance()
try
{
    // Do something with hashSet.
}
finally
{
    hashSet.Free();
}

Microsoft.IO.RecyclableMemoryStream

यह पुस्तकालय MemoryStreamवस्तुओं के लिए पूलिंग प्रदान करता है । यह एक ड्रॉप-इन प्रतिस्थापन है System.IO.MemoryStream। इसका ठीक वैसा ही शब्दार्थ है। इसे बिंग इंजीनियरों द्वारा डिजाइन किया गया था। यहाँ ब्लॉग पोस्ट पढ़ें या GitHub पर कोड देखें ।

var sourceBuffer = new byte[]{0,1,2,3,4,5,6,7}; 
var manager = new RecyclableMemoryStreamManager(); 
using (var stream = manager.GetStream()) 
{ 
    stream.Write(sourceBuffer, 0, sourceBuffer.Length); 
}

ध्यान दें कि RecyclableMemoryStreamManagerएक बार घोषित किया जाना चाहिए और यह पूरी प्रक्रिया के लिए रहेगा - यह पूल है। यदि आप चाहें तो कई पूलों का उपयोग करना पूरी तरह से ठीक है।


2
यह एक बेहतरीन जवाब है। C # 6 & VS2015 के बाद RTM है, मैं संभवतः इसे स्वीकृत उत्तर बनाऊंगा क्योंकि यह स्पष्ट रूप से सबसे अच्छा है अगर यह रोसेलिन द्वारा उपयोग किया जाता है।
क्रिस मैरिसिक

मैं सहमत हूं लेकिन आप किस कार्यान्वयन का उपयोग करेंगे? रोजलिन में तीन होते हैं। उत्तर में मेरे प्रश्न का लिंक देखें।
मुहम्मद रेहान सईद

1
लगता है कि प्रत्येक के पास बहुत स्पष्ट रूप से परिभाषित उद्देश्य हैं, केवल एक खुले हुए एक विकल्प की तुलना में बेहतर सभी आकार फिट बैठता है।
क्रिस मैरिसिक

1
ArrayPool के साथ @MuhammadRehanSaeed महान
क्रिस मैरिसिक

1
यह देखते हुए RecyclableMemoryStreamकि यह अल्ट्रा हाई परफॉर्मेंस ऑप्टिमाइजेशन के लिए एक अद्भुत अतिरिक्त है।
क्रिस मैरिसिक

315

यह सवाल कई अज्ञात लोगों के कारण होने वाली अपेक्षा की तुलना में थोड़ा पेचीदा है: संसाधन का व्यवहार पूल किया जा रहा है, वस्तुओं की अपेक्षित / अपेक्षित जीवनकाल, वास्तविक कारण जो पूल की आवश्यकता है, आदि। आमतौर पर पूल विशेष उद्देश्य-थ्रेड होते हैं। पूल, कनेक्शन पूल, इत्यादि - क्योंकि जब आप जानते हैं कि संसाधन क्या करता है और अधिक महत्वपूर्ण बात यह है कि उस संसाधन को कैसे लागू किया जाता है पर नियंत्रण करना आसान है।

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

एक सामान्य-उद्देश्य वाले पूल में कुछ मुख्य "सेटिंग" होनी चाहिए, जिनमें शामिल हैं:

  • संसाधन लोड करने की रणनीति - उत्सुक या आलसी;
  • संसाधन लोडिंग तंत्र - वास्तव में एक का निर्माण कैसे करें;
  • पहुंच की रणनीति - आप "राउंड रॉबिन" का उल्लेख करते हैं जो उतना सरल नहीं है जितना लगता है; यह कार्यान्वयन एक परिपत्र बफर का उपयोग कर सकता है जो समान है , लेकिन सही नहीं है, क्योंकि संसाधनों पर वास्तव में पुनः प्राप्त होने पर पूल का कोई नियंत्रण नहीं है। अन्य विकल्प FIFO और LIFO हैं; FIFO में एक यादृच्छिक-अभिगम पैटर्न अधिक होगा, लेकिन LIFO इसे कम से कम हाल ही में उपयोग की जाने वाली मुक्त रणनीति को लागू करना आसान बनाता है (जो आपने कहा था कि यह दायरे से बाहर है, लेकिन यह अभी भी ध्यान देने योग्य है)।

संसाधन लोडिंग तंत्र के लिए, .NET पहले से ही हमें एक साफ अमूर्त - प्रतिनिधि देता है।

private Func<Pool<T>, T> factory;

पूल के कंस्ट्रक्टर के माध्यम से इसे पास करें और हम उसी के साथ काम कर रहे हैं। एक new()बाधा के साथ एक सामान्य प्रकार का उपयोग करना भी काम करता है, लेकिन यह अधिक लचीला है।


अन्य दो मापदंडों में से, एक्सेस रणनीति अधिक जटिल जानवर है, इसलिए मेरा दृष्टिकोण एक वंशानुक्रम (इंटरफ़ेस) आधारित दृष्टिकोण का उपयोग करना था:

public class Pool<T> : IDisposable
{
    // Other code - we'll come back to this

    interface IItemStore
    {
        T Fetch();
        void Store(T item);
        int Count { get; }
    }
}

यहां अवधारणा सरल है - हम सार्वजनिक Poolवर्ग को थ्रेड-सुरक्षा जैसे सामान्य मुद्दों को संभालने देंगे , लेकिन प्रत्येक एक्सेस पैटर्न के लिए एक अलग "आइटम स्टोर" का उपयोग करें। LIFO को एक स्टैक द्वारा आसानी से दर्शाया जाता है, FIFO एक कतार है, और मैंने List<T>एक राउंड-रॉबिन एक्सेस पैटर्न का अनुमान लगाने के लिए एक और इंडेक्स पॉइंटर का उपयोग करके एक बहुत-अनुकूलित-लेकिन-शायद-पर्याप्त-पर्याप्त परिपत्र बफर कार्यान्वयन का उपयोग नहीं किया है ।

नीचे दी गई सभी कक्षाएं आंतरिक कक्षाएं हैं Pool<T>- यह एक शैली का विकल्प था, लेकिन चूंकि ये वास्तव में बाहर से उपयोग करने के लिए नहीं हैं Pool, इसलिए यह सबसे अधिक समझ में आता है।

    class QueueStore : Queue<T>, IItemStore
    {
        public QueueStore(int capacity) : base(capacity)
        {
        }

        public T Fetch()
        {
            return Dequeue();
        }

        public void Store(T item)
        {
            Enqueue(item);
        }
    }

    class StackStore : Stack<T>, IItemStore
    {
        public StackStore(int capacity) : base(capacity)
        {
        }

        public T Fetch()
        {
            return Pop();
        }

        public void Store(T item)
        {
            Push(item);
        }
    }

ये स्पष्ट हैं - स्टैक और कतार। मुझे नहीं लगता कि वे वास्तव में बहुत स्पष्टीकरण देते हैं। परिपत्र बफर थोड़ा अधिक जटिल है:

    class CircularStore : IItemStore
    {
        private List<Slot> slots;
        private int freeSlotCount;
        private int position = -1;

        public CircularStore(int capacity)
        {
            slots = new List<Slot>(capacity);
        }

        public T Fetch()
        {
            if (Count == 0)
                throw new InvalidOperationException("The buffer is empty.");

            int startPosition = position;
            do
            {
                Advance();
                Slot slot = slots[position];
                if (!slot.IsInUse)
                {
                    slot.IsInUse = true;
                    --freeSlotCount;
                    return slot.Item;
                }
            } while (startPosition != position);
            throw new InvalidOperationException("No free slots.");
        }

        public void Store(T item)
        {
            Slot slot = slots.Find(s => object.Equals(s.Item, item));
            if (slot == null)
            {
                slot = new Slot(item);
                slots.Add(slot);
            }
            slot.IsInUse = false;
            ++freeSlotCount;
        }

        public int Count
        {
            get { return freeSlotCount; }
        }

        private void Advance()
        {
            position = (position + 1) % slots.Count;
        }

        class Slot
        {
            public Slot(T item)
            {
                this.Item = item;
            }

            public T Item { get; private set; }
            public bool IsInUse { get; set; }
        }
    }

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

याद रखें, ये कक्षाएं निजी आंतरिक कक्षाएं हैं - यही कारण है कि उन्हें पूरी तरह से त्रुटि-जाँच की आवश्यकता नहीं है, पूल स्वयं उन तक पहुंच को प्रतिबंधित करता है।

एक एन्यूमरेशन और एक फैक्ट्री विधि में फेंकें और हम इस भाग के साथ काम करें:

// Outside the pool
public enum AccessMode { FIFO, LIFO, Circular };

    private IItemStore itemStore;

    // Inside the Pool
    private IItemStore CreateItemStore(AccessMode mode, int capacity)
    {
        switch (mode)
        {
            case AccessMode.FIFO:
                return new QueueStore(capacity);
            case AccessMode.LIFO:
                return new StackStore(capacity);
            default:
                Debug.Assert(mode == AccessMode.Circular,
                    "Invalid AccessMode in CreateItemStore");
                return new CircularStore(capacity);
        }
    }

समाधान करने के लिए अगली समस्या लोडिंग रणनीति है। मैंने तीन प्रकारों को परिभाषित किया है:

public enum LoadingMode { Eager, Lazy, LazyExpanding };

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

लोडिंग विधियां वास्तव में बहुत जटिल नहीं हैं, अब हमारे पास आइटम-स्टोर अमूर्त है:

    private int size;
    private int count;

    private T AcquireEager()
    {
        lock (itemStore)
        {
            return itemStore.Fetch();
        }
    }

    private T AcquireLazy()
    {
        lock (itemStore)
        {
            if (itemStore.Count > 0)
            {
                return itemStore.Fetch();
            }
        }
        Interlocked.Increment(ref count);
        return factory(this);
    }

    private T AcquireLazyExpanding()
    {
        bool shouldExpand = false;
        if (count < size)
        {
            int newCount = Interlocked.Increment(ref count);
            if (newCount <= size)
            {
                shouldExpand = true;
            }
            else
            {
                // Another thread took the last spot - use the store instead
                Interlocked.Decrement(ref count);
            }
        }
        if (shouldExpand)
        {
            return factory(this);
        }
        else
        {
            lock (itemStore)
            {
                return itemStore.Fetch();
            }
        }
    }

    private void PreloadItems()
    {
        for (int i = 0; i < size; i++)
        {
            T item = factory(this);
            itemStore.Store(item);
        }
        count = size;
    }

sizeऔर countखाने के ऊपर और पूल का अधिकतम आकार पूल (लेकिन जरूरी नहीं कि के स्वामित्व वाले संसाधनों की कुल संख्या का उल्लेख उपलब्ध क्रमशः),। AcquireEagerसबसे सरल है, यह मानता है कि एक आइटम पहले से ही स्टोर में है - ये आइटम निर्माण में पहले से लोड किए जाएंगे, अर्थात PreloadItemsपिछले अपडेट की गई विधि में।

AcquireLazyयह देखने के लिए कि क्या पूल में मुफ्त आइटम हैं, और यदि नहीं, तो यह एक नया निर्माण करता है। AcquireLazyExpandingजब तक पूल अपने लक्ष्य आकार तक नहीं पहुँच जाता है तब तक एक नया संसाधन तैयार करेगा। मैंने इसे लॉक करने को कम से कम करने के लिए इसे अनुकूलित करने की कोशिश की है, और मुझे आशा है कि मैंने कोई गलती नहीं की है (मैंने इसे बहु-थ्रेडेड परिस्थितियों में परीक्षण किया है, लेकिन स्पष्ट रूप से नहीं।

आप सोच रहे होंगे कि इनमें से कोई भी तरीका यह देखने के लिए जाँचने में परेशान नहीं करता कि दुकान अधिकतम आकार तक पहुँची है या नहीं। मैं एक पल में मिल जाएगा।


अब पूल के लिए ही। यहां निजी डेटा का पूरा सेट दिया गया है, जिनमें से कुछ पहले ही दिखाए जा चुके हैं:

    private bool isDisposed;
    private Func<Pool<T>, T> factory;
    private LoadingMode loadingMode;
    private IItemStore itemStore;
    private int size;
    private int count;
    private Semaphore sync;

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

कंस्ट्रक्टर इस तरह दिखता है:

    public Pool(int size, Func<Pool<T>, T> factory,
        LoadingMode loadingMode, AccessMode accessMode)
    {
        if (size <= 0)
            throw new ArgumentOutOfRangeException("size", size,
                "Argument 'size' must be greater than zero.");
        if (factory == null)
            throw new ArgumentNullException("factory");

        this.size = size;
        this.factory = factory;
        sync = new Semaphore(size, size);
        this.loadingMode = loadingMode;
        this.itemStore = CreateItemStore(accessMode, size);
        if (loadingMode == LoadingMode.Eager)
        {
            PreloadItems();
        }
    }

यहां कोई आश्चर्य नहीं होना चाहिए। केवल ध्यान देने योग्य बात यह है कि उत्सुक लोडिंग के लिए विशेष आवरण है, PreloadItemsपहले से दिखाए गए तरीके का उपयोग करके ।

चूंकि अब तक लगभग सब कुछ साफ-सुथरा हो चुका है, वास्तविक Acquireऔर Releaseविधियां वास्तव में बहुत सीधी हैं:

    public T Acquire()
    {
        sync.WaitOne();
        switch (loadingMode)
        {
            case LoadingMode.Eager:
                return AcquireEager();
            case LoadingMode.Lazy:
                return AcquireLazy();
            default:
                Debug.Assert(loadingMode == LoadingMode.LazyExpanding,
                    "Unknown LoadingMode encountered in Acquire method.");
                return AcquireLazyExpanding();
        }
    }

    public void Release(T item)
    {
        lock (itemStore)
        {
            itemStore.Store(item);
        }
        sync.Release();
    }

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

पिछले नहीं बल्कि कम से कम, वहाँ सफाई है:

    public void Dispose()
    {
        if (isDisposed)
        {
            return;
        }
        isDisposed = true;
        if (typeof(IDisposable).IsAssignableFrom(typeof(T)))
        {
            lock (itemStore)
            {
                while (itemStore.Count > 0)
                {
                    IDisposable disposable = (IDisposable)itemStore.Fetch();
                    disposable.Dispose();
                }
            }
        }
        sync.Close();
    }

    public bool IsDisposed
    {
        get { return isDisposed; }
    }

उस IsDisposedसंपत्ति का उद्देश्य एक पल में स्पष्ट हो जाएगा। Disposeयदि वे लागू करते हैं तो सभी मुख्य विधि वास्तव में वास्तविक पूलित वस्तुओं का निपटान करती है IDisposable


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

मान लें कि हम निम्नलिखित सरल अंतरफलक / वर्ग से शुरू करते हैं:

public interface IFoo : IDisposable
{
    void Test();
}

public class Foo : IFoo
{
    private static int count = 0;

    private int num;

    public Foo()
    {
        num = Interlocked.Increment(ref count);
    }

    public void Dispose()
    {
        Console.WriteLine("Goodbye from Foo #{0}", num);
    }

    public void Test()
    {
        Console.WriteLine("Hello from Foo #{0}", num);
    }
}

यहाँ हमारा दिखावा प्रयोज्य Fooसंसाधन है जो IFooअद्वितीय पहचान उत्पन्न करने के लिए कुछ बॉयलरप्लेट कोड लागू करता है और है। हम जो करते हैं वह एक और विशेष, पूलित वस्तु बनाने के लिए होता है:

public class PooledFoo : IFoo
{
    private Foo internalFoo;
    private Pool<IFoo> pool;

    public PooledFoo(Pool<IFoo> pool)
    {
        if (pool == null)
            throw new ArgumentNullException("pool");

        this.pool = pool;
        this.internalFoo = new Foo();
    }

    public void Dispose()
    {
        if (pool.IsDisposed)
        {
            internalFoo.Dispose();
        }
        else
        {
            pool.Release(this);
        }
    }

    public void Test()
    {
        internalFoo.Test();
    }
}

यह सिर्फ "वास्तविक" तरीकों के सभी को अपने भीतर तक पहुँचाता है IFoo(हम इसे कैसल की तरह एक डायनेमिक प्रॉक्सी लाइब्रेरी के साथ कर सकते हैं, लेकिन मैं इसमें नहीं मिलेगा)। यह एक संदर्भ भी रखता है Poolजो इसे बनाता है, ताकि जब हम Disposeइस वस्तु को बनाते हैं, तो यह स्वतः ही पूल में वापस आ जाता है। सिवाय जब पूल को पहले ही निपटा दिया गया है - इसका मतलब है कि हम "सफाई" मोड में हैं और इस मामले में यह वास्तव में इसके बजाय आंतरिक संसाधन को साफ करता है


उपरोक्त दृष्टिकोण का उपयोग करते हुए, हमें इस तरह कोड लिखना होगा:

// Create the pool early
Pool<IFoo> pool = new Pool<IFoo>(PoolSize, p => new PooledFoo(p),
    LoadingMode.Lazy, AccessMode.Circular);

// Sometime later on...
using (IFoo foo = pool.Acquire())
{
    foo.Test();
}

यह करने में सक्षम होने के लिए एक बहुत अच्छी बात है। इसका मतलब है कि जो कोड का उपयोग करता हैIFoo (कोड के विपरीत जो इसे बनाता है) वास्तव में पूल के बारे में पता करने की आवश्यकता नहीं है। तुम भी अपने पसंदीदा DI पुस्तकालय और प्रदाता / कारखाने के रूप में वस्तुओं का इंजेक्शन लगा सकते हैं ।IFooPool<T>


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

अगर आपको इस बारे में कोई सवाल या चिंता है तो मुझे बताएं।


62
एसओ पर मैंने जो सबसे पूरा, सहायक, और दिलचस्प जवाब पढ़ा है।
जोश स्मेटन

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

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

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

5
बहुत प्रभावशाली, हालांकि अधिकांश स्थितियों के लिए थोड़ा अधिक इंजीनियर। मैं उम्मीद करूंगा कि ऐसा कुछ हो, जो एक ढांचे का हिस्सा हो।
चोसपंडियन

7

ऐसा कुछ आपकी आवश्यकताओं के अनुरूप हो सकता है।

/// <summary>
/// Represents a pool of objects with a size limit.
/// </summary>
/// <typeparam name="T">The type of object in the pool.</typeparam>
public sealed class ObjectPool<T> : IDisposable
    where T : new()
{
    private readonly int size;
    private readonly object locker;
    private readonly Queue<T> queue;
    private int count;


    /// <summary>
    /// Initializes a new instance of the ObjectPool class.
    /// </summary>
    /// <param name="size">The size of the object pool.</param>
    public ObjectPool(int size)
    {
        if (size <= 0)
        {
            const string message = "The size of the pool must be greater than zero.";
            throw new ArgumentOutOfRangeException("size", size, message);
        }

        this.size = size;
        locker = new object();
        queue = new Queue<T>();
    }


    /// <summary>
    /// Retrieves an item from the pool. 
    /// </summary>
    /// <returns>The item retrieved from the pool.</returns>
    public T Get()
    {
        lock (locker)
        {
            if (queue.Count > 0)
            {
                return queue.Dequeue();
            }

            count++;
            return new T();
        }
    }

    /// <summary>
    /// Places an item in the pool.
    /// </summary>
    /// <param name="item">The item to place to the pool.</param>
    public void Put(T item)
    {
        lock (locker)
        {
            if (count < size)
            {
                queue.Enqueue(item);
            }
            else
            {
                using (item as IDisposable)
                {
                    count--;
                }
            }
        }
    }

    /// <summary>
    /// Disposes of items in the pool that implement IDisposable.
    /// </summary>
    public void Dispose()
    {
        lock (locker)
        {
            count = 0;
            while (queue.Count > 0)
            {
                using (queue.Dequeue() as IDisposable)
                {

                }
            }
        }
    }
}

उदाहरण उपयोग

public class ThisObject
{
    private readonly ObjectPool<That> pool = new ObjectPool<That>(100);

    public void ThisMethod()
    {
        var that = pool.Get();

        try
        { 
            // Use that ....
        }
        finally
        {
            pool.Put(that);
        }
    }
}

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

1
@ चेतावनी - क्या यह वास्तव में अजीब है? मैं एक लाइटवेट पूल बनाना चाहता था जो केवल आवश्यक कार्यक्षमता प्रदान करता है। यह क्लाइंट पर निर्भर है कि वह क्लास का सही इस्तेमाल करे।
ChaosPandion

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

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

1
@ क्रिस - मैं बस एक सरल उपकरण प्रदान कर रहा हूं जो मैंने अतीत में उपयोगी पाया है। बाकी आप पर निर्भर करता है। जैसा कि आप फिट देखते हैं, कोड को संशोधित और उपयोग करें।
चोसपंडियन

6

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

नाइस एंड सिंपल
डैनियल डे ज़्वान

4

दिन में Microsoft ने Microsoft ऑब्जेक्ट्स के लिए ऑब्जेक्ट पूलिंग करने के लिए Microsoft Transaction Server (MTS) और बाद में COM + के माध्यम से एक फ्रेमवर्क प्रदान किया। उस कार्यक्षमता को .NET फ्रेमवर्क में और अब विंडोज कम्युनिकेशन फाउंडेशन में System.EnterpriseServices के लिए आगे ले जाया गया।

WCF में ऑब्जेक्ट पूलिंग

यह आलेख .NET 1.1 से है, लेकिन फिर भी फ्रेमवर्क के वर्तमान संस्करणों में लागू होना चाहिए (भले ही WCF पसंदीदा तरीका हो)।

ऑब्जेक्ट पूलिंग .NET


+1 यह दिखाने के लिए कि IInstanceProviderइंटरफ़ेस मौजूद है क्योंकि मैं इसे अपने समाधान के लिए लागू करूंगा। मैं हमेशा एक Microsoft द्वारा प्रदान किए गए इंटरफ़ेस के पीछे मेरे कोड को स्टैक करने का प्रशंसक हूं जब वे एक उपयुक्त परिभाषा प्रदान करते हैं।
क्रिस मैरिसिक

4

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

  1. बदलें sync.WaitOne()करने के लिएsync.WaitOne(timeout) और पर एक पैरामीटर के रूप टाइमआउट बेनकाब Acquire(int timeout)विधि। यह भी स्थिति को संभालने की आवश्यकता होगी जब थ्रेड आउट किसी वस्तु पर उपलब्ध होने की प्रतीक्षा कर रहा हो।
  2. Recycle(T item)उदाहरण के लिए, किसी वस्तु के पुनर्नवीनीकरण की आवश्यकता होने पर स्थितियों को संभालने के लिए विधि जोड़ें ।

3

यह एक और कार्यान्वयन है, पूल में सीमित संख्या में वस्तुओं के साथ।

public class ObjectPool<T>
    where T : class
{
    private readonly int maxSize;
    private Func<T> constructor;
    private int currentSize;
    private Queue<T> pool;
    private AutoResetEvent poolReleasedEvent;

    public ObjectPool(int maxSize, Func<T> constructor)
    {
        this.maxSize = maxSize;
        this.constructor = constructor;
        this.currentSize = 0;
        this.pool = new Queue<T>();
        this.poolReleasedEvent = new AutoResetEvent(false);
    }

    public T GetFromPool()
    {
        T item = null;
        do
        {
            lock (this)
            {
                if (this.pool.Count == 0)
                {
                    if (this.currentSize < this.maxSize)
                    {
                        item = this.constructor();
                        this.currentSize++;
                    }
                }
                else
                {
                    item = this.pool.Dequeue();
                }
            }

            if (null == item)
            {
                this.poolReleasedEvent.WaitOne();
            }
        }
        while (null == item);
        return item;
    }

    public void ReturnToPool(T item)
    {
        lock (this)
        {
            this.pool.Enqueue(item);
            this.poolReleasedEvent.Set();
        }
    }
}

3

जावा उन्मुख, यह लेख कनेक्शनइम्प्ल पूल पैटर्न और अमूर्त ऑब्जेक्ट पूल पैटर्न को उजागर करता है और एक अच्छा पहला दृष्टिकोण हो सकता है: http://www.developer.com/design/article.php/626171/Pattern-Summeries-Object-Pool। htm

ऑब्जेक्ट पूल पैटर्न:

पैटर्न



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