একাধিক অজানা কারণে এই প্রশ্নটি যতটা আশা করা যায় তার চেয়ে একটু জটিল: পুল, সংযোগ পুল ইত্যাদি - কারণ যখন আপনি জানেন যে উত্সটি ঠিক কী করে এবং কীভাবে সেই সংস্থানটি কীভাবে কার্যকর হয় তার উপর নিয়ন্ত্রণ আরো গুরুত্বপূর্ণ তখন একটিটিকে অনুকূলকরণ করা সহজ ।
যেহেতু এটি সহজ নয়, তাই আমি যা করার চেষ্টা করেছি তা হ'ল একটি নমনীয় দৃষ্টিভঙ্গি উপস্থাপন করা যা আপনি পরীক্ষা করতে পারেন এবং কোনটি সবচেয়ে ভাল কাজ করে তা দেখতে পারেন। দীর্ঘ পোস্টের জন্য অগ্রিম ক্ষমাপ্রার্থী, তবে যখন কোনও শালীন সাধারণ-উদ্দেশ্য সম্পন্ন রিসোর্স পুল বাস্তবায়নের কথা আসে তখন প্রচুর ক্ষেত্র রয়েছে। এবং আমি সত্যিই কেবল পৃষ্ঠকে আঁচড় দিচ্ছি।
একটি সাধারণ উদ্দেশ্যে পুলটিতে কয়েকটি প্রধান "সেটিংস" থাকতে হবে, যার মধ্যে রয়েছে:
- সংস্থান লোড কৌশল - উত্সাহী বা অলস;
- রিসোর্স লোডিং মেকানিজম - আসলে কীভাবে একটি তৈরি করবেন;
- অ্যাক্সেস কৌশল - আপনি "রাউন্ড রবিন" উল্লেখ করেছেন যা এটি শোনাতে সোজা নয়; এই প্রয়োগটি একটি বিজ্ঞপ্তিযুক্ত বাফার ব্যবহার করতে পারে যা অনুরূপ , তবে নিখুঁত নয়, কারণ সম্পদগুলি আসলে পুনরুদ্ধার করা হলে পুলটির কোনও নিয়ন্ত্রণ থাকে না। অন্যান্য বিকল্পগুলি হল ফিফো এবং লিফো; ফিফোর কাছে এলোমেলো-অ্যাক্সেস প্যাটার্ন থাকবে, তবে লিফো একটি স্বল্প-সাম্প্রতিক-ব্যবহৃত নিখরচায় কৌশল প্রয়োগ করা (যা আপনি বলেছিলেন যে সুযোগের বাইরে ছিল, তবে এটি এখনও উল্লেখ করার মতো নয়) কার্যকর করে তোলে।
রিসোর্স লোডিং প্রক্রিয়াটির জন্য,। নেট ইতিমধ্যে আমাদের একটি পরিষ্কার বিমূর্ততা দেয় - প্রতিনিধিরা।
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
ক্লাসকে থ্রেড-সুরক্ষার মতো সাধারণ সমস্যাগুলি পরিচালনা করতে দেব , তবে প্রতিটি অ্যাক্সেস প্যাটার্নের জন্য আলাদা "আইটেম স্টোর" ব্যবহার করব। লিফো সহজেই একটি স্ট্যাক দ্বারা প্রতিনিধিত্ব করা হয়, ফিফো একটি সারি এবং আমি 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);
}
}
এগুলি হ'ল সুস্পষ্ট - স্ট্যাক এবং সারি। আমি মনে করি না যে তারা সত্যিকার অর্থে খুব বেশি ব্যাখ্যা দেয় warrant বিজ্ঞপ্তি বাফারটি আরও জটিল:
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 এর জন্য ইতিমধ্যে একটি দুর্দান্ত সরঞ্জাম রয়েছে, এটি সেমফোর বলে এবং এটি নির্দিষ্ট করার অনুমতি দেওয়ার জন্য বিশেষভাবে ডিজাইন করা হয়েছে কোনও সংস্থানটিতে থ্রেডের অ্যাক্সেসের সংখ্যা (এই ক্ষেত্রে "রিসোর্স" অভ্যন্তরীণ আইটেমের স্টোর) যেহেতু আমরা একটি পূর্ণ অন উত্পাদনকারী / গ্রাহক সারি বাস্তবায়ন করছি না, এটি আমাদের প্রয়োজনের জন্য পুরোপুরি পর্যাপ্ত।
কনস্ট্রাক্টর এর মত দেখাচ্ছে:
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
(যেমন কোড যা সৃষ্টি থেকে ভিন্ন) আসলে পুকুর সচেতন হতে হবে দরকার নেই। এমনকি আপনি আপনার প্রিয় ডিআই লাইব্রেরি এবং সরবরাহকারী / কারখানা হিসাবে অবজেক্টগুলি ইনজেক্ট করতে পারেন ।IFoo
Pool<T>
আমি আপনার অনুলিপি এবং পেস্ট করার জন্য পুরো কোডটি পেস্টবিনে রেখেছি । এটি একটি থ্রেড-নিরাপদ এবং বগি নয় বলে নিজেকে সন্তুষ্ট করতে বিভিন্ন লোডিং / অ্যাক্সেস মোড এবং মাল্টিথ্রেডেড শর্তগুলির সাথে চারপাশে খেলতে আপনি একটি ছোট পরীক্ষা প্রোগ্রামও ব্যবহার করতে পারেন।
আপনার যদি এর কোনও সম্পর্কে কোনও প্রশ্ন বা উদ্বেগ থাকে তবে আমাকে জানান।