Управление на паметта и ресурсите


Техниката “пулинг на ресурси”



страница7/7
Дата25.02.2017
Размер0.85 Mb.
#15739
1   2   3   4   5   6   7

Техниката “пулинг на ресурси”


Пулинг на ресурси (resource pooling) е програмна техника за подобряване на производителността при работа с ресурси, които се създават или унищожават “скъпо” (бавно).

Тази техника се използва от много рамки на приложения (frameworks), включително от COM+ за да не създава и унищожава непрекъснато обекти, а в .NET Framework – при управлението на най-разнообразни, най-вече неуправляеми ресурси като:



  • връзки към бази данни (connection pooling);

  • нишки (thread pooling);

  • и др.

Пулът обикновено представлява списък от обекти, които се създават предварително (например при инициализация на приложението), а после се “раздават” при поискване. Клиентите взимат обекти от пула, използват ги известно време и след като вече не им трябват, не ги унищожават, а ги връщат обратно в пула.

Дизайнът на пула може да бъде различен, според нуждите на вашето приложение:



  • типизиран (само за определен тип обекти) или не;

  • изискващ имплементация на определен интерфейс от обектите или не;

  • позволяващ или не инстанциране на пула, т.е. Сек17 (Singleton) както System.ThreadPool или обикновен клас;

  • позволяващ или не задаване на броя на предварително създадените обекти в пула;

  • с ограничен брой елементи (като System.ThreadPool) или безкраен, като при свършване на елементите в пула се създават и добавят нови;

  • безопасен за работа в многонишково приложение (thread-safe) или такъв, който трябва да се синхронизира ръчно от потребителя;

  • разширяем (който може да се наследява) или не (sealed class);

  • създаващ обектите вътрешно (ако е типизиран) или посредством стратегия за тяхното създаване, например делегат (delegate).

Примерна имплементация на пул от ресурси


Ще ви дадем пример за разширяем, нетипизиран, thread-safe пул от обекти, чието създаване се осъществява или посредством обект-стратегия18 или посредством прототипна инстанция.

Ако пулът е инициализиран с прототип, се създава стратегия по подраз­биране и пулът се инициализира с нея. Обектът-прототип трябва да имплементира следния интерфейс:



public interface IObjectPrototype

{

// Обектът връща пълно копие на себе си



IObjectPrototype DeepClone();

}


Обектът-стратегия трябва да имплементира интерфейс, позволяващ дина­мичното създаване и унищожаване на обекти за целите на пула:

public interface IObjectPoolStrategy

{

// Създава нов обект



object Create();
// Унищожава обект, създаден от Create

void Destroy(object aInstance);

}


Както споменахме, ако пулът е инициализиран с прототип, ще използва стратегията по подразбиране за създаване на обекти и елементарна имплементация на унищожаването им:

// Потребителите на пула могат да дефинират клас,

// който наследява DefaultObjectPoolStrategy за да

// не имплементират целия интерфейс

public class DefaultObjectPoolStrategy : IObjectPoolStrategy

{

// Ако наследниците не използват прототип, просто подават null



public DefaultObjectPoolStrategy(IObjectPrototype aPrototype)

{

mPrototype = aPrototype;



}
// Ако наследниците не предефинират метода,

// се използва прототипа

public virtual object Create()

{

if (mPrototype == null)



{

// Едновременното непредефиниране на метода

// и неподаването на прототип е грешка

string message = String.Format(

"{0} instantiated without prototype",

GetType().Name);

throw new InvalidOperationException(message);

}
return mPrototype.DeepClone();

}
// Имплементация по подразбиране на Destroy метода

public virtual void Destroy(object instance)

{

if (instance != null)



{

// Ако обектът имплементира IDisposable го унищожаваме

IDisposable disp = instance as IDisposable;

if (disp != null)

{

disp.Dispose();



}

}

}


private IObjectPrototype mPrototype;

}


Ето и имплементация на пула. Тя съдържа подробни коментари и затова няма да я обсъждаме по-нататък.

// След инстанциране, с пула се работи чрез:

// метода Draw() за извличане на обект от пула, например:

// object obj = pool.Draw();

// метода Return() за връщане на обект в пула, например:

// pool.Return(obj);

// свойството IsLimited, което връща дали пулът е ограничен

// Наследяваме ResourceWrapperBase за да извикаме Destroy()

// за всички обекти в пула, когато той се унищожава

public class ObjectPool : ResourceWrapperBase

{

private const int DEFAULT_MIN_OBJECTS = 4;



private const int UNLIMITED = -1;
// обект-стратегия за създаване и унищожаване на обекти

private IObjectPoolStrategy mStrategy;


private Stack mItems; // хранилище за обектите в пула

private int mInitialObjects; // първоначален брой обекти

private int mMaxObjects; // максимален брой обекти
// Създава обекти чрез прототип. Пулът е неограничен, а

// първоначалният брой е по подразбиране

public ObjectPool(IObjectPrototype aPrototype) :

this(aPrototype, DEFAULT_MIN_OBJECTS)

{

}
// Създава aInitialObjects обекта предварително



// чрез прототип. Пулът е неограничен

public ObjectPool(IObjectPrototype aPrototype,

int aInitialObjects) :

this(aPrototype, aInitialObjects, UNLIMITED)

{

}
// Създава aInitialObjects обекта чрез прототип.



// Пулът е ограничен до aMaxObjects

public ObjectPool(IObjectPrototype aPrototype,

int aInitialObjects, int aMaxObjects)

{

if (aPrototype == null)



{

throw new ArgumentNullException("prototype");

}
// Действителната инициализация се случва в Init()

Init(aInitialObjects, aMaxObjects, aPrototype, null);

}
// Създава обекти чрез стратегия. Пулът е неограничен.

// Първоначалният брой е по подразбиране

public ObjectPool(IObjectPoolStrategy aStrategy) :

this(aStrategy, DEFAULT_MIN_OBJECTS)

{

}
// Създава aInitialObjects обекта чрез стратегия.



// Пулът е неограничен

public ObjectPool(IObjectPoolStrategy aStrategy,

int aInitialObjects) :

this(aStrategy, aInitialObjects, UNLIMITED)

{

}
// Създава aInitialObjects обекти чрез стратегия.



// Пулът е ограничен до aMaxObjects

public ObjectPool(IObjectPoolStrategy aStrategy,

int aInitialObjects, int aMaxObjects)

{

if (aStrategy == null)



{

throw new ArgumentNullException("strategy");

}
Init(aInitialObjects, aMaxObjects, null, aStrategy);

}
// Метод за инициализация. Извиква се от конструкторите

private void Init(int aInitialObjects,

int aMaxObjects, IObjectPrototype aPrototype,

IObjectPoolStrategy aStrategy)

{

// Ако aMaxObjects == UNLIMITED, пулът е неограничен



if (aInitialObjects < 0 ||

(aMaxObjects != UNLIMITED &&

aMaxObjects < aInitialObjects))

{

throw new ArgumentException(



"initialObjects < 0 or maxObjects < initialObjects");

}
mInitialObjects = aInitialObjects;

mMaxObjects = aMaxObjects;

mStrategy = aStrategy;

if (mStrategy == null)

{

mStrategy = new DefaultObjectPoolStrategy(aPrototype);



}

// Създаваме минимума обекти

mItems = new Stack(mMaxObjects);

for (int i = 0; i < mInitialObjects; ++i)

{

mItems.Push(CreateObject());



}

}
// Връща дали пулът е ограничен

public bool IsLimited

{

get



{

return mMaxObjects != UNLIMITED;

}

}
// Извлича свободна инстанция от пула. Ако пулът е



// празен, се разширява, а ако не може да се разшири,

// се получава изключение

public object Draw()

{

lock (mItems)



{

// Тук сме сигурни, че се изпълнява само една нишка

if (mItems.Count > 0)

{

return mItems.Pop();



}

// Тук сме попаднали, ако първоначално или

// след заключването е нямало свободни елементи.

// Проверяваме може ли да разширим пула

int itemsToAdd;

if (mMaxObjects == UNLIMITED)

{

itemsToAdd = 1;



}

else


{

itemsToAdd = mMaxObjects - mInitialObjects;

if (itemsToAdd == 0)

{

throw new InvalidOperationException(



"The pull is empty and can not grow further.");

}

}


for (int i = 0; i < itemsToAdd; ++i)

{

mItems.Push(CreateObject());



}

// Гарантирано е, че имаме поне един елемент в пула

return mItems.Pop();

}

}


// Връща дадена инстанция обратно в пула

public void Return(object aInstance)

{

if (aInstance == null)



{

throw new ArgumentNullException("instance");

}

lock (mItems)



{

mItems.Push(aInstance);

}

}
// Наследниците на пула могат да предефинират



// създаването и унищожаването на обекти

protected virtual object CreateObject()

{

return mStrategy.Create();



}
protected virtual void DestroyObject(object aInstance)

{

mStrategy.Destroy(aInstance);



}
// Метод от ResourceWrapperBase - унищожава обектите в пула

protected override void DisposeManagedResources()

{

foreach (object instance in mItems)



{

DestroyObject(instance);

}

base.DisposeManagedResources();



}

}


Класът ObjectPool може да бъде подобрен, особено по отношение на работата му в многонишкова конкурентна среда:

  • Можете да добавите събитие (AutoResetEvent), което се сигнализира при наличието на обект в пула и да промените кода на Draw, така че да разчита на това събитие и при празен пул да чака изчаква докато някой върне обект в пула.

  • Можете да добавите метод object TryDraw(int milliseconds), който да се опитва да получи обект от пула в рамките на интервала, посочен в milliseconds, след което да изхвърли изключение или да върне null.

Всичко това оставяме като ужасяващо упражнение за читателя.

Упражнения


  1. Какво знаете за автоматичното управление на паметта и ресурсите в .NET Framework? Какви са предимствата и недостатъците на автоматичното управление на паметта? Как работи т. нар. garbage collector?

  2. Какво знаете за финализацията и интерфейса IDisposable в .NET Framework? Кога се използват? Как се реализират?

  3. С помощта на класа ResourceWrapperBase реализирайте обвивка на неуправлявания ресурс “TODO”.

  4. Напишете клас BufferedConsole, който предоставя буфериран изход към конзолата чрез метода си Write(string). Класът трябва да съдържа в себе си буфер с размер 50 байта, в който се добавят изпратените низове. При препълване на буфера данните от него трябва да се отпечатват на конзолата. Имплементирайте финализация и IDisposable и при почистване на ресурсите отпечатвайте буфера на конзолата.

  5. Реализирайте примерна програма, която използва класа BufferedConsole за да печата различни съобщения в конзолата. Използвайте конструкцията using в C# за да освободите правилно инстанцията на класа BufferedConsole.

  6. Реализирайте правилно освобождаване на инстанцията на BufferedConsole от предходната задача без да използвате конструкцията using, а чрез try...finally конструкция.

  7. Реализирайте примерна програма, която печата по конзолата чрез класа BufferedConsole и разчита на финализацията за да не се губят данните от буфера при почистване на паметта. Защо този подход трябва да се избягва пред възможността ресурсите да се почистят ръчно?

  8. Реализирайте метод, който по дадени цели числа N и K връща броя на комбинациите без повторение от N елемента, K-ти клас. Използвайте за изчисленията триъгълника на Паскал и слаби референции, в които съхранявайте отделните му редове.

  9. Реализирайте прост пул от обекти от тип Resource. Пулът трябва да не е защитен от конкурен­тен достъп (thread unsafe), да няма ограничение за броя създадени едновременно обекти, да не създава предварително никакви обекти и да съхранява освободените инстанции в стек.

Използвана литература


  • Георги Иванов, Управление на паметта и ресурсите – http://www.nakov.com/dotnet/2003/lectures/Memory-management-finalization.doc

  • Jeffrey Richter, Applied Microsoft .NET Framework Programming, Microsoft Press, 2002, ISBN 0735614229

  • Tushar Agrawal, Memory Management in .NET – http://www.c-sharpcorner.com/Code/2003/Nov/MemoryManagementInNet.asp

  • MSDN Training, Programming with the Microsoft® .NET Framework (MOC 2349B), Module 9: Memory and Resource Management

  • MSDN Library – http://msdn.microsoft.com

  • MSDN Magazine, Jeffrey Richter, Garbage Collection: Automatic Memory Management in the Microsoft .NET Framework – http://msdn.microsoft.com/ msdnmag/issues/1100/GCI/default.aspx

  • MSDN Magazine, Jeffrey Richter, Garbage Collection – Part 2: Automatic Memory Management in the Microsoft .NET Framework – http://msdn.microsoft. com/msdnmag/issues/1200/GCI2/




1 Като изключим напълно валидната стратегия на бездействие

2 Съществуват и други функции, като calloc(), а също и нестандартни, като alloca(), която заделя памет от стека

3 При използване на масиви се използват операторите new[] и delete[]

4 За примитивните типове като int не се извиква конструктор/деструктор

5 С помощта на оператора “placement new” може да създадете обект на произволен адрес в паметта, включително и в стека

6 Resource Acquisition Is Initialization

7 Подробно обяснение ще намерите в края на тази тема

8 Мениджърът на памет Hoard, решава до голяма степен горните три проблема, а също има и средства за намиране на утечки на памет, но за съжаление не е безплатен.

9 Това естествено зависи от наличието на памет, тъй като не ресурсите, а манипулаторите (handles) за ресурсите са ограничени

10 “Най-находчивите” от вас могат да опорочат защитата (помислете как)

11 Виж шаблона template method в книгата “Шаблони за дизайн”

12 Имплементацията на CompareExchange() използва специална инструк­ция с префикс на процесорите на Intel (lock cmpxchg) поради което работи много бързо

13 Повече информация ще намерите в глава 16

14 Такава програма има в Unix (и подобните й ОС) и се нарича tail

15 JIT компилаторът няма достатъчно време за да прави агресивни оптими­зации, каквито може да направи компилаторът

16 вж. “Шаблони за дизайн”, изд. SoftPress

17 виж “Шаблони за дизайн”, шаблон “Сек”

18 виж “Шаблони за дизайн”, шаблон “Стратегия”




Сподели с приятели:
1   2   3   4   5   6   7




©obuch.info 2024
отнасят до администрацията

    Начална страница