Объектный пул
Объектный пул | |
---|---|
Object pool | |
Тип | порождающий |
Описан в Design Patterns | Нет |
Объектный пул (англ. object pool) — порождающий шаблон проектирования, набор инициализированных и готовых к использованию объектов. Когда системе требуется объект, он не создаётся, а берётся из пула. Когда объект больше не нужен, он не уничтожается, а возвращается в пул.
Применение
Объектный пул применяется для повышения производительности, когда создание объекта в начале работы и уничтожение его в конце приводит к большим затратам. Особенно заметно повышение производительности, когда объекты часто создаются-уничтожаются, но одновременно существует лишь небольшое их число.
Объектный пул удобен, если объект владеет другими ресурсами, кроме памяти — например, сетевыми сокетами. Либо если коллекция объектов отнимает значительную часть памяти компьютера и «мусора» создаётся действительно много.
Переполнение
Если в пуле нет ни одного свободного объекта, возможна одна из трёх стратегий:
- Расширение пула.
- Отказ в создании объекта, аварийная остановка.
- В случае многозадачной системы можно подождать, пока один из объектов не освободится.
Примеры
- Информация об открытых файлах в DOS.
- Информация о видимых объектах во многих компьютерных играх (хорошим примером является движок Doom). Эта информация актуальна только в течение одного кадра; после того, как кадр выведен, список опустошается.
- Компьютерная игра для хранения всех объектов на карте, вместо того, чтобы использовать обычные механизмы распределения памяти, может завести массив такого размера, которого заведомо хватит на все объекты, и свободные ячейки держать в виде связного списка. Такая конструкция повышает скорость, уменьшает фрагментацию памяти и снижает нагрузку на сборщик мусора (если он есть).
Ловушки
- После того, как объект возвращён, он должен вернуться в состояние, пригодное для дальнейшего использования. Если объекты после возвращения в пул оказываются в неправильном или неопределённом состоянии, такая конструкция называется объектной клоакой (англ. object cesspool).
- Повторное использование объектов также может привести к утечке информации. Если в объекте есть секретные данные (например, номер кредитной карты), после освобождения объекта эту информацию надо затереть.
- Многопоточный объектный пул написать не так просто.
- На 2020-е годы в языках со сбором мусора управление памятью хорошо оптимизировано под постоянное выделение-отдачу. Так что, если объект занимает только память, руководства по Java не рекомендуют пользоваться пулами: обычный
new
требует всего десять процессорных команд. А сборщики мусора часто сканируют ссылки на объекты, а не их память — потому чем больше в памяти «живых» объектов, тем ниже производительность такого сборщика.
Пример реализации
Пример на 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++
#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#
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();
}
}
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();
}
}
}
}
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)
{
}
}
}
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
Namespace 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
Namespace 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
Namespace 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
Imports 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
Пример на Perl
#!/usr/bin/perl -w
=for comment
Модуль ObjectPool реализовывает паттерн программирования "объектный пул" путём моделирования
поведения лучника, в колчане у которого имеется ограниченное количество стрел, в результате
чего ему приходится периодически их подбирать
Пакет описывает поведение лучника
=cut
package Archer {
use Quiver; # колчан с стрелами лучника
use strict;
use warnings;
use constant ARROWS_NUMBER => 5; # количество стрел в колчане
use constant SLEEP_TIME => 3; # максимальный перерыв между двумя действиями (в секундах)
# -- ** констркутор ** --
sub new {
my $class = shift;
my $self = {
quiver => Quiver->new(ARROWS_NUMBER), # объект класса "Колчан"
};
bless $self, $class;
return $self;
}
# -- ** инициализация стрельбы ** --
sub shooting_start {
my($self) = (shift);
while (1) { # условно бесконечный цикл, в котором ведется стрельба
$self->shoot() for (0 .. rand(ARROWS_NUMBER - 1)); # случайное количество выстрелов
$self->reload() for (0 .. rand(ARROWS_NUMBER - 1)); # случайное количество возвратов отстрелянных стрел
}
}
# -- ** выстрел ** --
sub shoot {
my($self) = (shift);
$self->{quiver}->arrow_pull(); # отправляем стрелу куда подальше
sleep rand(SLEEP_TIME); # ... и пребываем в ожидании на протяжении неопределенного промежутка времени
}
# -- ** возвращение выпущеной стрелы ** --
sub reload {
my($self) = (shift);
$self->{quiver}->arrow_return(); # возвращаем выпущенную ранее стрелу
sleep rand(SLEEP_TIME); # и опять находимся в ожидании
}
}
$archer = Archer->new(); # бравый стрелок берет свой колчан со стрелами
$archer->shooting_start(); # ... и начинает стрелять
=for comment
Пакет описывает свойства колчана, которым пользуется лучник (Archer) и в котором хранятся стрелы (Arrow)
=cut
package Quiver {
use Arrow; # одна стрела из колчана
use feature "say";
use strict;
use warnings;
# -- ** конструктор ** --
sub new {
my($class, $arrows_number) = (shift, shift);
my $self = {
arrows => [], # стрелы в колчане (их пока нет, но скоро они там появятся)
};
bless $self, $class;
$self->arrows_prepare($arrows_number); # загрузить стрелы в колчан
return $self;
}
# -- ** приготовление стрел к стрельбе ** --
sub arrows_prepare {
my($self, $arrows_number) = (shift, shift);
push @{$self->{arrows}}, Arrow->new($_) for (0 .. $arrows_number - 1); # укладываем стрелы в колчан
}
# -- ** извлечение стрелы из колчана ** --
sub arrow_pull {
my($self) = (shift);
foreach(@{$self->{arrows}}) { # для каждой стрелы проверяем, в колчане ли она
if($_->check_state()) { # и если - да
$_->pull(); # вынимаем её оттуда (и стреляем)
last; # двумя стрелами одновременно мы выстрелить не сможем
}
}
}
# -- ** возвращение стрелы в колчан ** --
sub arrow_return {
my($self) = (shift);
foreach(@{$self->{arrows}}) { # для каждой стрелы проверяем, не выпущена ли она уже
if(!$_->check_state()) { # если в колчане такой стрелы нет
$_->return(); # идем и подбираем её
last; # в теории лучник может подобрать больше одной стрелы за раз, но автор считает иначе
}
}
}
}
1;
=for comment
Пакет описывает свойства одной отдельной стрелы, находящейся в колчане (Quiver) лучника (Archer)
=cut
package Arrow {
use feature "say";
use strict;
use warnings;
# -- ** конструктор ** --
sub new {
my $class = shift;
my $self = {
number => shift, # номер стрелы
state => 1, # состояние стрелы (1 = в колчане, 0 = где-то валяется после выстрела)
};
bless $self, $class;
return $self;
}
# -- ** изъять стрелу из колчана ** --
sub pull {
my($self) = (shift);
$self->{state} = 0; # изменить состояние стрелы на "выпущена"
say "pulled $self->{number}"; # сообщить о том, что выстрел состоялся
}
# -- ** вернуть стрелу обратно в колчан ** --
sub return {
my($self) = (shift);
$self->{state} = 1; # изменить состояние стрелы на "в колчане"
say "returned $self->{number}"; # сообщить о том, что стрела вернулась к лучнику
}
# -- ** проверить состояние стрелы ** --
sub check_state {
my($self) = (shift);
return $self->{state}; # вернуть состояние стрелы (1 = в колчане, 0 = выпущена)
}
}
1;
Ссылки
- Паттерн Object Pool (пул объектов) — назначение, описание, структура, особенности применения