امروز تصمیم گرفتم تا در رابطه با الگوی طراحی Object pool مطلبی بنویسم و تجربیاتم رو از استفاده این الگو به اشتراک بزارم. من سعی میکنم تا میتونم نوشته ها رو خلاصه کنم و با طرح مسئله ای که منجر به بروز الگو شده مطلب رو توضیح بدم تا درک اون ساده تر بشه و اگر جایی نیاز به کدزنی باشه از سی شارپ برای این کار استفاده میکنم.
چند سال پیش قرار شد تا یه نرم افزار گرافیکی طراحی کنم, بعد از گذشت یه بازه زمانی متوجه شدم اپلیکیشن به نسبت کند هستش, مجبور شدم بشینم کد رو مرور کنم, مصرف مموری و cpu رو کامل بررسی کنم, خیلی سریع دیدم که توی بخشی از کد ها نرم افزار نیاز داره تا از یک نوع کلاس آبجکت های زیادی رو تولید میکنه که بعد از ایجاد آبجکت و استفاده خیلی سریع اون آبجکت Dispose میشه, مشکل از همینجا شروع میشد که Initialize کردن این آبجکت زمان زیادی میگرفت و طول عمر آبجکت خیلی کوتاه!(تا اینجا رو داشته باشید تا بریم سراغ مسئله بعدی)
یک مثال دیگه که ما خیلی به این الگو نیاز پیدا میکنم نرم افزار هایی هستش که در اون socket connections های زیادی رو باید هندل کنیم(برای این که از بحث دور نشیم بیشتر این موضوع رو باز نمیکنم)
خب تا اینجا دیدم که مسئله اصلی چی بود که باعث شد تا نرم افزار گرافیکی ما کند بشه, نوبت حل مسئله رسید, اینجا بود دست به دامن یک الگوی طراحی به اسم Object pool شدیم. اگه بخوام تو یه خط توضیحش بدم:
این الگوی طراحی رو توی موقعیت هایی استفاده میکنیم که زمان ایجاد یک آبجکت خیلی بالا باشه
روشی که برای حل این مسئله پیشنهاد میشه این هستش که ما میایم یک Container ایجاد میکنیم که شامل یک تعداد مشخصی از آبجکت هایی هستش که زمان ایجاد بالایی رو دارن, وقتی جایی نیاز به آبجکتی از کلاس مربوطه داشته باشیم از این Container برمیدارم و تا زمانی که کارمون باهاش تموم نشده فقط و فقط در اختیار ما هستش و بعد از این که کارمون باهاش تموم شد برش میگردونیم قرارش میدیم توی Container.
در اینجا کلاس Client کلاسی هستش که نیاز به استفاده آبجکت داره, Reuseable کلاسی هستش که زمان Initialize کردن بالایی داره(برای مثال SqlConnectin, ایجاد Thread و یا کلاس گرافیکی نرم افزار ما) و در آخر کلاس ReusablePool اصلی ترین کلاس توی این الگو هستش, این کلاس شامل لیستی از آبجکت هایی هستش که میتونیم به کلاس Client بدیم و لیستی از آبجکت هایی که در حال استفاده هستند.
یک مثالی که برنامه نویس های دات نت و جاوا خیلی باهاش درگیر هستن ایجاد آبجکتی برای اتصال به دیتابیس هستش, در صورتی که برنامه ما اتصال به دیتابیس زیادی داشته باشه ایجاد connection جدید و از بین بردن اون شدیدا روی کارایی برنامه تاثیر منفی میزاره و توان سیستم در پردازش تعداد درخواست همزمان رو به شدت پایین میاره و موضوع دوم هم اگر ما همزمان connection های باز زیادی داشته باشیم یک سربار به دیتابیس اضافه میشه که باعث میشه تا دیتابیس هم تحت تاثیر قرار بگیره.
برای حل این مشکل توی سی شارپ از کلاسی مثل زیر استفاده میکنیم:
public class ObjectPool<T> { private ConcurrentBag<T> _objects; private Func<T> _objectGenerator; public ObjectPool(Func<T> objectGenerator) { if (objectGenerator == null) throw new ArgumentNullException("objectGenerator"); _objects = new ConcurrentBag<T>(); _objectGenerator = objectGenerator; } public T GetObject() { T item; if (_objects.TryTake(out item)) return item; return _objectGenerator(); } public void PutObject(T item) { _objects.Add(item); } }
دلیل استفاده از ConcurrentBag این هستش که میخوایم به صورت Parallel کد رو اجرا کنیم. در اینجا صرفا یه لیست ایجاد کردیم و زمانی که نیاز به یه آبجکت داشته باشیم و در لیست وجود نداشته باشه یه آبجکت جدید ایجاد میکنیم و وقتی کارمون با آبجکت تموم شد توی لیست قرارش میدیم تا درخواست بعدی از آبجکت استفاده کنه.
برای این که تاثیر این الگو رو ببیند من سعی کردم تا کلاسی که ایجاد میکنم مصرف مموری بالایی داشته باشه:
class MyClass { public int[] Nums { get; set; } public long GetValue(long i) { return Nums[i]; } public MyClass() { Nums = new int[100000]; Random rand = new Random(); for (int i = 0; i < Nums.Length; i++) Nums[i] = rand.Next(); } }
همون طوی که میبینید این کلاس هم مصرف حافظه بالایی داره هم زمان ایجاد یک آبجکت جدید از این کلاس زمان زیادی رو میگیره(روی سیستم من چیزی در حدود 4 میلی ثانیه), خب بریم سراغ کد اصلی که میخواد از ترکیب این دو استفاده کنه:
ObjectPool<MyClass> pool = new ObjectPool<MyClass>(() => new MyClass()); Parallel.For(0, 10000, (i, loopState) => { MyClass mc = pool.GetObject(); Console.CursorLeft = 0; Console.WriteLine(mc.GetValue(i)); pool.PutObject(mc); });
ده هزار آبجکت رو به صورت همزمان توی ترد های مختلف اجرا میکنیم و سعی میکنم از object pool استفاده کنیم, برای این که تاثیر این کار رو ببینم سعی میکنم همین تعداد آبجکت رو در حالت عادی هم ایجاد کنیم:
for (int i = 0; i <= 10000; i++) { MyClass mc = new MyClass(); Console.WriteLine(mc.GetValue(i)); }
من این دو تا کد رو اجرا کردم و میزان مصرف حافظه هر دو رو با همدیگه مقایسه کردم اگر دوست داشتین خودتون میتونیم زمان اجرا رو هم خودتون مقایسه کنید: