- Объектный пул
-
Шаблон проектирования Объектный пул Object pool Тип: порождающий
Описан в Design Patterns Нет
Объектный пул (англ. object pool) — порождающий шаблон проектирования, набор инициализированных и готовых к использованию объектов. Когда системе требуется объект, он не создаётся, а берётся из пула. Когда объект больше не нужен, он не уничтожается, а возвращается в пул.
Содержание
Применение
Объектный пул применяется для повышения производительности, когда создание объекта в начале работы и уничтожение его в конце приводит к большим затратам. Особенно заметно повышение производительности, когда объекты часто создаются-уничтожаются, но одновременно существует лишь небольшое их число.
Переполнение
Если в пуле нет ни одного свободного объекта, возможна одна из трёх стратегий:
- Расширение пула.
- Отказ в создании объекта, аварийный останов.
- В случае многозадачной системы, можно подождать, пока один из объектов не освободится.
Примеры
- Информация об открытых файлах в DOS.
- Информация о видимых объектах во многих компьютерных играх (хорошим примером является движок Doom). Эта информация актуальна только в течение одного кадра; после того, как кадр выведен, список опустошается.
- Компьютерная игра для хранения всех объектов на карте, вместо того, чтобы использовать обычные механизмы распределения памяти, может завести массив такого размера, которого заведомо хватит на все объекты, и свободные ячейки держать в виде связного списка. Такая конструкция повышает скорость, уменьшает фрагментацию памяти и снижает нагрузку на сборщик мусора (если он есть).
Ловушки
- После того, как объект возвращён, он должен вернуться в состояние, пригодное для дальнейшего использования. Если объекты после возвращения в пул оказываются в неправильном или неопределённом состоянии, такая конструкция называется объектной клоакой (англ. object cesspool).
- Повторное использование объектов также может привести к утечке информации. Если в объекте есть секретные данные (например, номер кредитной карты), после освобождения объекта эту информацию надо затереть.
Пример реализации
Пример на Python
Исходный текст на языке Python#coding: utf-8 """ Представим ситуацию, что у нас есть корабль, который может выдержать несколько выстрелов. Создание объекта Shot стоит дорого. Поэтому объекты семейства Shot создаём 1 раз. А по истечении жизни объект остаётся в памяти. """ class Shot(object): """ Сущность, способная пережить несколько попаданий """ def __init__(self, lifetime=5): self.lifetime = lifetime def update(self): self.lifetime -= 1 return self.lifetime > 0 class ObjectPool: """ Пул объектов """ def __init__(self, **kwargs): """ Создание пула """ self._clsname = kwargs['classname'] self._args = kwargs.get('args', []) self._num_objects = max(kwargs['num'], 0) self._pred = kwargs['update_func'] self._max_objects = kwargs.get('max', self._num_objects) # Create the objects self._objs = [apply(self._clsname, self._args) for x in range(self._num_objects)] self._end = len(self._objs) def _extend_list(self, args): """ Добавить одно место в пул """ self._objs.append(apply(self._clsname, args)) self._num_objects += 1 def add(self, *args): """ Добавить один объект в пул """ newend = self._end + 1 #Если достигнут максимум - отбой if newend > self._max_objects: return None #Если заняли все места - добавляем еще одно место if newend > len(self._objs): self._extend_list(args) else: self._objs[self._end].reset(*args) self._end += 1 return self._end - 1 def update(self, *args): """ Обновить все объекты в пуле """ self._end = partition(self._pred, self._objs, 0, self._end, args) return self._end def update_object(x): """ Обновить объект """ return x.update() def partition(pred, seq, first, last, *args): """ Функция сортировки объектов """ if first > last: return 0 for i in range(first, last): if not pred(seq[i]): break else: return last for j in range(i+1, last): if pred(seq[j]): seq[i], seq[j] = seq[j], seq[i] i += 1 return i """ Собственно использование пула """ shots = ObjectPool(classname=Shot, update_func=update_object, num=5) while shots.update(): pass print "Done!"
Пример на C++
Исходный текст на языке C++#include <vector> class Object { // ... }; class ObjectPool { private: struct PoolRecord { Object* instance; bool in_use; }; std::vector<PoolRecord> m_pool; public: Object* createNewObject() { for (size_t i = 0; i < m_pool.size(); ++i) { if (! m_pool[i].in_use) { m_pool[i].in_use = true; // переводим объект в список используемых return m_pool[i].instance; } } // если не нашли свободный объект, то расширяем пул PoolRecord record; record.instance = new Object; record.in_use = true; m_pool.push_back(record); return record.instance; } void deleteObject(Object* object) { // в реальности не удаляем, а лишь помечаем, что объкт свободен for (size_t i = 0; i < m_pool.size(); ++i) { if (m_pool[i].instance == object) { m_pool[i].in_use = false; break; } } } virtual ~ObjectPool() { // теперь уже "по-настоящему" удаляем объекты for (size_t i = 0; i < m_pool.size(); ++i) delete m_pool[i].instance; } }; int main() { ObjectPool pool; for (size_t i = 0; i < 1000; ++i) { Object* object = pool.createNewObject(); // ... pool.deleteObject(object); } return 0; }
Из примера для простоты убраны шаблоны и потокозащищенность. При необходимости использования пула в нескольких потоках следует защитить тело методов createNewObject и deleteObject от одновременного выполнения каким-либо подходящим объектом синхронизации, например, критической секцией или мьютексом.
Пример на C#
Исходный текст на языке C#namespace Digital_Patterns.Creational.Object_Pool.Soft { /// <summary> /// Интерфейс для использования шаблона "Object Pool" <see cref="Object_Pool"/> /// </summary> /// <typeparam name="T"></typeparam> public interface ICreation<T> { /// <summary> /// Возвращает вновь созданный объект /// </summary> /// <returns></returns> T Create(); } }
Исходный текст на языке C#using System; using System.Collections; using System.Threading; namespace Digital_Patterns.Creational.Object_Pool.Soft { /// <summary> /// Реализация пула объектов, использующий "мягкие" ссылки /// </summary> /// <typeparam name="T"></typeparam> public class ObjectPool<T> where T : class { /// <summary> /// Объект синхронизации /// </summary> private Semaphore semaphore; /// <summary> /// Коллекция содержит управляемые объекты /// </summary> private ArrayList pool; /// <summary> /// Ссылка на объект, которому делегируется ответственность /// за создание объектов пула /// </summary> private ICreation<T> creator; /// <summary> /// Количество объектов, существующих в данный момент /// </summary> private Int32 instanceCount; /// <summary> /// Максимальное количество управляемых пулом объектов /// </summary> private Int32 maxInstances; /// <summary> /// Создание пула объектов /// </summary> /// <param name="creator">Объект, которому пул будет делегировать ответственность /// за создание управляемых им объектов</param> public ObjectPool(ICreation<T> creator) : this(creator, Int32.MaxValue) { } /// <summary> /// Создание пула объектов /// </summary> /// <param name="creator">Объект, которому пул будет делегировать ответственность /// за создание управляемых им объектов</param> /// <param name="maxInstances">Максимальное количество экземпляров класс, /// которым пул разрешает существовать одновременно /// </param> public ObjectPool(ICreation<T> creator, Int32 maxInstances) { this.creator = creator; this.instanceCount = 0; this.maxInstances = maxInstances; this.pool = new ArrayList(); this.semaphore = new Semaphore(0, this.maxInstances); } /// <summary> /// Возвращает количество объектов в пуле, ожидающих повторного /// использования. Реальное количество может быть меньше /// этого значения, поскольку возвращаемая /// величина - это количество "мягких" ссылок в пуле. /// </summary> public Int32 Size { get { lock(pool) { return pool.Count; } } } /// <summary> /// Возвращает количество управляемых пулом объектов, /// существующих в данный момент /// </summary> public Int32 InstanceCount { get { return instanceCount; } } /// <summary> /// Получить или задать максимальное количество управляемых пулом /// объектов, которым пул разрешает существовать одновременно. /// </summary> public Int32 MaxInstances { get { return maxInstances; } set { maxInstances = value; } } /// <summary> /// Возвращает из пула объект. При пустом пуле будет создан /// объект, если количество управляемых пулом объектов не /// больше или равно значению, возвращаемому методом /// <see cref="ObjectPool{T}.MaxInstances"/>. Если количество управляемых пулом /// объектов превышает это значение, то данный метод возварщает null /// </summary> /// <returns></returns> public T GetObject() { lock(pool) { T thisObject = RemoveObject(); if (thisObject != null) return thisObject; if (InstanceCount < MaxInstances) return CreateObject(); return null; } } /// <summary> /// Возвращает из пула объект. При пустом пуле будет создан /// объект, если количество управляемых пулом объектов не /// больше или равно значению, возвращаемому методом /// <see cref="ObjectPool{T}.MaxInstances"/>. Если количество управляемых пулом /// объектов превышает это значение, то данный метод будет ждать до тех /// пор, пока какой-нибудь объект не станет доступным для /// повторного использования. /// </summary> /// <returns></returns> public T WaitForObject() { lock(pool) { T thisObject = RemoveObject(); if (thisObject != null) return thisObject; if (InstanceCount < MaxInstances) return CreateObject(); } semaphore.WaitOne(); return WaitForObject(); } /// <summary> /// Удаляет объект из коллекции пула и возвращает его /// </summary> /// <returns></returns> private T RemoveObject() { while (pool.Count > 0 ) { var refThis = (WeakReference) pool[pool.Count - 1]; pool.RemoveAt(pool.Count - 1); var thisObject = (T)refThis.Target; if (thisObject != null) return thisObject; instanceCount--; } return null; } /// <summary> /// Создать объект, управляемый этим пулом /// </summary> /// <returns></returns> private T CreateObject() { T newObject = creator.Create(); instanceCount++; return newObject; } /// <summary> /// Освобождает объект, помещая его в пул для /// повторного использования /// </summary> /// <param name="obj"></param> /// <exception cref="NullReferenceException"></exception> public void Release(T obj) { if(obj == null) throw new NullReferenceException(); lock(pool) { var refThis = new WeakReference(obj); pool.Add(refThis); semaphore.Release(); } } } }
Исходный текст на языке C#namespace Digital_Patterns.Creational.Object_Pool.Soft { public class Reusable { public Object[] Objs { get; protected set; } public Reusable(params Object[] objs) { this.Objs = objs; } } public class Creator : ICreation<Reusable> { private static Int32 iD = 0; public Reusable Create() { ++iD; return new Reusable(iD); } } public class ReusablePool : ObjectPool<Reusable> { public ReusablePool() : base(new Creator(), 2) { } } }
Исходный текст на языке C#using System; using System.Threading; using Digital_Patterns.Creational.Object_Pool.Soft; namespace Digital_Patterns { class Program { static void Main(string[] args) { Console.WriteLine(System.Reflection.MethodInfo.GetCurrentMethod().Name); var reusablePool = new ReusablePool(); var thrd1 = new Thread(Run); var thrd2 = new Thread(Run); var thisObject1 = reusablePool.GetObject(); var thisObject2 = reusablePool.GetObject(); thrd1.Start(reusablePool); thrd2.Start(reusablePool); ViewObject(thisObject1); ViewObject(thisObject2); Thread.Sleep(2000); reusablePool.Release(thisObject1); Thread.Sleep(2000); reusablePool.Release(thisObject2); Console.ReadKey(); } private static void Run(Object obj) { Console.WriteLine("\t" + System.Reflection.MethodInfo.GetCurrentMethod().Name); var reusablePool = (ReusablePool)obj; Console.WriteLine("\tstart wait"); var thisObject1 = reusablePool.WaitForObject(); ViewObject(thisObject1); Console.WriteLine("\tend wait"); reusablePool.Release(thisObject1); } private static void ViewObject(Reusable thisObject) { foreach (var obj in thisObject.Objs) { Console.Write(obj.ToString() + @" "); } Console.WriteLine(); } } }
Пример на VB.NET
Исходный текст на языке VB.NETNamespace Digital_Patterns.Creational.Object_Pool.Soft ' Интерфейс для использования шаблона "Object Pool" <see cref="Object_Pool"/> Public Interface ICreation(Of T) ' Возвращает вновь созданный объект Function Create() As T End Interface End Namespace
Исходный текст на языке VB.NETNamespace Digital_Patterns.Creational.Object_Pool.Soft 'Реализация пула объектов, использующий "мягкие" ссылки Public Class ObjectPool(Of T As Class) 'Объект синхронизации Private semaphore As Semaphore 'Коллекция содержит управляемые объекты Private pool As ArrayList 'Ссылка на объект, которому делегируется ответственность за создание объектов пула Private creator As ICreation(Of T) 'Количество объектов, существующих в данный момент Private m_instanceCount As Int32 'Максимальное количество управляемых пулом объектов Private m_maxInstances As Int32 'Созданчие пула объектов ' creator - объект, которому пул будет делегировать ответственность за создание управляемых им объектов Public Sub New(ByVal creator As ICreation(Of T)) Me.New(creator, Int32.MaxValue) End Sub 'Создание пула объектов ' creator - Объект, которому пул будет делегировать ответственность за создание управляемых им объектов ' maxInstances - Максимальное количество экземпляров класс, которым пул разрешает существовать одновременно Public Sub New(ByVal creator As ICreation(Of T), ByVal maxInstances As Int32) Me.creator = creator Me.m_instanceCount = 0 Me.m_maxInstances = maxInstances Me.pool = New ArrayList() Me.semaphore = New Semaphore(0, Me.m_maxInstances) End Sub 'Возвращает количество объектов в пуле, ожидающих повторного 'использования. Реальное количество может быть меньше 'этого значения, поскольку возвращаемая 'величина - это количество "мягких" ссылок в пуле. Public ReadOnly Property Size() As Int32 Get SyncLock pool Return pool.Count End SyncLock End Get End Property 'Возвращает количество управляемых пулом объектов, 'существующих в данный момент Public ReadOnly Property InstanceCount() As Int32 Get Return m_instanceCount End Get End Property 'Получить или задать максимальное количество управляемых пулом 'объектов, которым пул разрешает существовать одновременно. Public Property MaxInstances() As Int32 Get Return m_maxInstances End Get Set(ByVal value As Int32) m_maxInstances = value End Set End Property 'Возвращает из пула объект. При пустом пуле будет создан 'объект, если количество управляемых пулом объектов не 'больше или равно значению, возвращаемому методом ObjectPool{T}.MaxInstances. 'Если количество управляемых пулом объектов превышает это значение, то данный 'метод возварщает null Public Function GetObject() As T SyncLock pool Dim thisObject As T = RemoveObject() If thisObject IsNot Nothing Then Return thisObject End If If InstanceCount < MaxInstances Then Return CreateObject() End If Return Nothing End SyncLock End Function ' Возвращает из пула объект. При пустом пуле будет создан ' объект, если количество управляемых пулом объектов не ' больше или равно значению, возвращаемому методом ObjectPool{T}.MaxInstances ' Если количество управляемых пулом объектов превышает это значение, ' то данный метод будет ждать до тех пор, пока какой-нибудь объект ' не станет доступным для повторного использования. Public Function WaitForObject() As T SyncLock pool Dim thisObject As T = RemoveObject() If thisObject IsNot Nothing Then Return thisObject End If If InstanceCount < MaxInstances Then Return CreateObject() End If End SyncLock semaphore.WaitOne() Return WaitForObject() End Function ' Удаляет объект из коллекции пула и возвращает его Private Function RemoveObject() As T While pool.Count > 0 Dim refThis = DirectCast(pool(pool.Count - 1), WeakReference) pool.RemoveAt(pool.Count - 1) Dim thisObject = DirectCast(refThis.Target, T) If thisObject IsNot Nothing Then Return thisObject End If m_instanceCount -= 1 End While Return Nothing End Function ' Создать объект, управляемый этим пулом Private Function CreateObject() As T Dim newObject As T = creator.Create() m_instanceCount += 1 Return newObject End Function ' Освобождает объект, помещая его в пул для повторного использования Public Sub Release(ByVal obj As T) If obj Is Nothing Then Throw New NullReferenceException() End If SyncLock pool Dim refThis = New WeakReference(obj) pool.Add(refThis) semaphore.Release() End SyncLock End Sub End Class End Namespace
Исходный текст на языке VB.NETNamespace Digital_Patterns.Creational.Object_Pool.Soft '### Класс Reusable #### Public Class Reusable Private m_Objs As Object() Public Sub New(ByVal ParamArray objs As Object()) Me.Objs = objs End Sub Public Property Objs() As Object() Get Return m_Objs End Get Protected Set(ByVal value As Object()) m_Objs = value End Set End Property End Class '### Класс Creator #### Public Class Creator Implements ICreation(Of Reusable) Private Shared iD As Int32 = 0 Public Function Create() As Reusable Implements ICreation(Of Reusable).Create iD += 1 Return New Reusable(iD) End Function End Class '### Класс ReusablePool #### Public Class ReusablePool Inherits ObjectPool(Of Reusable) Public Sub New() MyBase.New(New Creator(), 2) End Sub End Class End Namespace
Исходный текст на языке VB.NETImports System.Threading Imports Digital_Patterns.Creational.Object_Pool.Soft Namespace Digital_Patterns Class Program Shared Sub Main() Console.WriteLine(System.Reflection.MethodInfo.GetCurrentMethod().Name) Dim reusablePool = New ReusablePool() Dim thrd1 = New Thread(AddressOf Run) Dim thrd2 = New Thread(AddressOf Run) Dim thisObject1 = reusablePool.GetObject() Dim thisObject2 = reusablePool.GetObject() thrd1.Start(reusablePool) thrd2.Start(reusablePool) ViewObject(thisObject1) ViewObject(thisObject2) Thread.Sleep(2000) reusablePool.Release(thisObject1) Thread.Sleep(2000) reusablePool.Release(thisObject2) Console.ReadKey() End Sub Private Shared Sub Run(ByVal obj As [Object]) Console.WriteLine(vbTab & System.Reflection.MethodInfo.GetCurrentMethod().Name) Dim reusablePool = DirectCast(obj, ReusablePool) Console.WriteLine(vbTab & "start wait") Dim thisObject1 = reusablePool.WaitForObject() ViewObject(thisObject1) Console.WriteLine(vbTab & "end wait") reusablePool.Release(thisObject1) End Sub Private Shared Sub ViewObject(ByVal thisObject As Reusable) For Each obj As Object In thisObject.Objs Console.Write(obj.ToString() & " ") Next Console.WriteLine() End Sub End Class End Namespace
Ссылки
- Паттерн Object Pool (пул объектов) — назначение, описание, структура, особенности применения
Шаблоны проектирования Основные Порождающие Абстрактная фабрика • Объектный пул • Одиночка • Отложенная инициализация • Прототип • Строитель • Фабричный метод
Структурные Поведенческие Интерпретатор • Итератор • Команда • Наблюдатель • Посетитель • Посредник • Состояние • Стратегия • Хранитель • Цепочка обязанностей • Шаблонный метод
Блокировка с двойной проверкой • Однопоточное выполнение • Планировщик Категории:- Шаблоны проектирования
- Структуры данных
Wikimedia Foundation. 2010.