Шаблон пула объектов - Object pool pattern

В шаблон пула объектов это программное обеспечение творческий шаблон дизайна который использует набор инициализированных объекты готов к использованию - "бассейн "- вместо того, чтобы выделять и уничтожать их по запросу. Клиент пула будет запрашивать объект из пула и выполнять операции с возвращенным объектом. Когда клиент завершает работу, он возвращает объект в пул, а не разрушая это; это можно сделать вручную или автоматически.

Пулы объектов в основном используются для повышения производительности: в некоторых случаях пулы объектов значительно повышают производительность. Пулы объектов усложняют время жизни объекта, поскольку объекты, полученные из пула и возвращенные в него, на самом деле не создаются и не уничтожаются в это время, и поэтому требуют осторожности при реализации.

Описание

Когда необходимо работать с большим количеством объектов, создание экземпляров которых особенно дорого, и каждый объект нужен только в течение короткого периода времени, производительность всего приложения может ухудшиться. Шаблон проектирования пула объектов может считаться желательным в таких случаях.

Шаблон проектирования пула объектов создает набор объектов, которые можно использовать повторно. Когда нужен новый объект, он запрашивается из пула. Если ранее подготовленный объект доступен, он немедленно возвращается, избегая затрат на создание экземпляра. Если в пуле нет объектов, создается и возвращается новый элемент. Когда объект был использован и больше не нужен, он возвращается в пул, что позволяет снова использовать его в будущем без повторения дорогостоящего в вычислительном отношении процесса создания экземпляра. Важно отметить, что после того, как объект был использован и возвращен, существующие ссылки станут недействительными.

В некоторых пулах объектов ресурсы ограничены, поэтому указывается максимальное количество объектов. Если это число достигается и запрашивается новый элемент, может возникнуть исключение или поток будет заблокирован до тех пор, пока объект не будет возвращен в пул.

Шаблон проектирования пула объектов используется в нескольких местах в стандартных классах .NET Framework. Одним из примеров является поставщик данных .NET Framework для SQL Server. Поскольку соединения с базой данных SQL Server могут создаваться медленно, пул соединений сохраняется. Закрытие соединения фактически не отменяет связь с SQL Server. Вместо этого соединение хранится в пуле, из которого его можно получить при запросе нового соединения. Это существенно увеличивает скорость установления соединений.

Льготы

Объединение объектов может предложить значительный прирост производительности в ситуациях, когда стоимость инициализации экземпляра класса высока, а скорость создания и уничтожения класса высока - в этом случае объекты можно часто повторно использовать, и каждое повторное использование экономит значительное количество ресурсов. время. Для объединения объектов требуются ресурсы - память и, возможно, другие ресурсы, такие как сетевые сокеты, и поэтому предпочтительно, чтобы количество экземпляров, используемых в любой момент времени, было небольшим, но это не требуется.

Объединенный объект получается в предсказуемое время, когда создание новых объектов (особенно по сети) может занять переменное время. Эти преимущества в основном справедливы для объектов, которые являются дорогостоящими по времени, таких как соединения с базой данных, соединения сокетов, потоки и большие графические объекты, такие как шрифты или растровые изображения.

В других ситуациях простой пул объектов (который не содержит внешних ресурсов, а только занимает память) может быть неэффективным и снизить производительность.[1] В случае простого пула памяти размещение плиты Метод управления памятью больше подходит, поскольку единственная цель - минимизировать затраты на выделение и освобождение памяти за счет уменьшения фрагментации.

Реализация

Пулы объектов могут быть реализованы автоматически на таких языках, как C ++, с помощью умные указатели. В конструкторе интеллектуального указателя объект может быть запрошен из пула, а в деструкторе интеллектуального указателя объект может быть выпущен обратно в пул. В языках со сборкой мусора, где нет деструкторов (которые гарантированно будут вызываться как часть раскрутки стека), пулы объектов должен быть реализовано вручную, путем явного запроса объекта из фабрика и возвращая объект, вызывая метод удаления (как в шаблон утилизации ). С помощью финализатор делать это - не лучшая идея, поскольку обычно нет никаких гарантий относительно того, когда (или если) финализатор будет запущен. Вместо этого следует использовать «попробуйте ... наконец», чтобы гарантировать, что получение и освобождение объекта не зависит от исключений.

Ручные пулы объектов легко реализовать, но их сложнее использовать, поскольку они требуют ручное управление памятью объектов бассейнов.

Обработка пустых бассейнов

Пулы объектов используют одну из трех стратегий для обработки запроса, когда в пуле нет запасных объектов.

  1. Не удалось предоставить объект (и вернуть клиенту ошибку).
  2. Выделите новый объект, увеличив таким образом размер пула. Пулы, которые делают это, обычно позволяют установить высокий уровень (максимальное количество когда-либо использовавшихся объектов).
  3. В многопоточный среды, пул может блокировать клиента до тех пор, пока другой поток не вернет объект в пул.

Ловушки

При написании пула объектов программист должен быть осторожен, чтобы убедиться, что состояние объектов, возвращаемых в пул, сброшено обратно до разумного состояния для следующего использования объекта. Если это не соблюдается, объект часто будет находиться в каком-то состоянии, которое было неожиданным для клиентской программы, и может вызвать сбой клиентской программы. Пул отвечает за сброс объектов, а не клиентов. Пулы объектов, заполненные объектами с опасно устаревшим состоянием, иногда называют выгребными ямами объектов и рассматривают как антипаттерн.

Наличие устаревшего состояния не всегда является проблемой; это становится опасным, когда наличие устаревшего состояния заставляет объект вести себя иначе. Например, объект, представляющий детали аутентификации, может сломаться, если флаг «успешно аутентифицирован» не сброшен до того, как он будет передан, поскольку он будет указывать на то, что пользователь правильно аутентифицирован (возможно, как кто-то другой), когда он еще не попытался для аутентификации. Однако он будет работать нормально, если вы не сбросите какое-то значение, используемое только для отладки, например, идентификатор последнего использованного сервера аутентификации.

Неадекватный сброс объектов также может вызвать утечку информации. Если объект содержит конфиденциальные данные (например, номера кредитных карт пользователя), которые не очищаются до передачи объекта новому клиенту, злонамеренный или ошибочный клиент может раскрыть данные неавторизованной стороне.

Если пул используется несколькими потоками, ему могут потребоваться средства, предотвращающие захват и попытки параллельного использования одного и того же объекта параллельно параллельными потоками. В этом нет необходимости, если объединенные объекты неизменяемы или иным образом ориентированы на потоки.

Критика

В некоторых публикациях не рекомендуется использовать объединение объектов с определенными языками, например Ява, особенно для объектов, которые используют только память и не содержат внешних ресурсов[который? ]. Противники обычно говорят, что распределение объектов относительно быстрое в современных языках с сборщики мусора; в то время как оператор новый нужно всего десять инструкций, классический новый - Удалить пара, найденная в проектах объединения, требует их сотен, поскольку она выполняет более сложную работу. Кроме того, большинство сборщиков мусора сканируют «живые» ссылки на объекты, а не память, которую эти объекты используют для своего содержимого. Это означает, что любое количество «мертвых» объектов без ссылок может быть отброшено с небольшими затратами. Напротив, сохранение большого количества «живых», но неиспользуемых объектов увеличивает продолжительность сборки мусора.[1]

Примеры

Идти

Следующий код Go инициализирует пул ресурсов указанного размера (параллельная инициализация), чтобы избежать проблем с гонкой ресурсов по каналам, а в случае пустого пула устанавливает обработку тайм-аута, чтобы клиенты не ожидали слишком долго.

// пул пакетовпакет бассейнимпорт (	"ошибки"	"журнал"	"математика / ранд"	"синхронизировать"	"время")const getResMaxTime = 3 * время.Второйвар (	ErrPoolNotExist  = ошибки.Новый("пул не существует")	ErrGetResTimeout = ошибки.Новый("получить время ожидания ресурса"))//Ресурстип Ресурс структура {	resId int}// NewResource Имитация медленного создания инициализации ресурса// (например, соединение TCP, получение симметричного ключа SSL, аутентификация по аутентификации требуют много времени)func NewResource(мне бы int) *Ресурс {	время.Спать(500 * время.Миллисекунды)	вернуть &Ресурс{resId: мне бы}}// Выполнение моделирования ресурсов требует времени, а случайное потребление составляет 0 ~ 400 мс.func (р *Ресурс) Делать(workId int) {	время.Спать(время.Продолжительность(ранд.Intn(5)) * 100 * время.Миллисекунды)	журнал.Printf("с использованием ресурса #% d законченная работа% d finish  n", р.resId, workId)}// Пул на основе реализации канала Go, чтобы избежать проблемы состояния гонки ресурсовтип Бассейн чан *Ресурс// Создать пул ресурсов указанного размера// Ресурсы создаются одновременно, чтобы сэкономить время инициализации ресурсаfunc Новый(размер int) Бассейн {	п := сделать(Бассейн, размер)	wg := новый(синхронизировать.WaitGroup)	wg.Добавить(размер)	для я := 0; я < размер; я++ {		идти func(resId int) {			п <- NewResource(resId)			wg.Выполнено()		}(я)	}	wg.Подождите()	вернуть п}// GetResource основан на канале, состояние гонки ресурсов избегается, а тайм-аут получения ресурсов установлен для пустого пулаfunc (п Бассейн) GetResource() (р *Ресурс, ошибаться ошибка) {	Выбрать {	кейс р := <-п:		вернуть р, ноль	кейс <-время.После(getResMaxTime):		вернуть ноль, ErrGetResTimeout	}}// GiveBackResource возвращает ресурсы в пул ресурсовfunc (п Бассейн) GiveBackResource(р *Ресурс) ошибка {	если п == ноль {		вернуть ErrPoolNotExist	}	п <- р	вернуть ноль}// основной пакетпакет основнойимпорт (	"github.com/tkstorm/go-design/creational/object-pool/pool"	"журнал"	"синхронизировать")func основной() {	// Инициализируем пул из пяти ресурсов,	// который можно изменить на 1 или 10, чтобы увидеть разницу	размер := 5	п := бассейн.Новый(размер)	// Вызывает ресурс для выполнения задания id	Выполнять работу := func(workId int, wg *синхронизировать.WaitGroup) {		отложить wg.Выполнено()		// Получаем ресурс из пула ресурсов		res, ошибаться := п.GetResource()		если ошибаться != ноль {			журнал.Println(ошибаться)			вернуть		}		// Ресурсы для возврата		отложить п.GiveBackResource(res)		// Используем ресурсы для выполнения работы		res.Делать(workId)	}	// Имитация 100 параллельных процессов для получения ресурсов из пула активов	число := 100	wg := новый(синхронизировать.WaitGroup)	wg.Добавить(число)	для я := 0; я < число; я++ {		идти Выполнять работу(я, wg)	}	wg.Подождите()}

C #

В .NET Библиотека базового класса есть несколько объектов, реализующих этот шаблон. System.Threading.ThreadPool настроен так, чтобы иметь заранее определенное количество потоков для распределения. Когда потоки возвращаются, они доступны для другого вычисления. Таким образом, можно использовать потоки, не платя за создание и удаление потоков.

Ниже показан базовый код шаблона проектирования пула объектов, реализованный с использованием C #. Для краткости свойства классов объявляются с использованием синтаксиса автоматически реализуемых свойств C # 3.0. Их можно заменить полными определениями свойств для более ранних версий языка. Пул показан как статический класс, поскольку требуется несколько пулов. Однако также приемлемо использовать классы экземпляров для пулов объектов.

пространство имен DesignPattern.Objectpool {    // Класс PooledObject - это тип, который является дорогостоящим или медленным для создания экземпляра,    // или имеющий ограниченную доступность, поэтому должен храниться в пуле объектов.    общественный класс PooledObject    {        частный DateTime _создано на = DateTime.Сейчас же;         общественный DateTime Создано на        {            получить { вернуть _создано на; }        }         общественный строка TempData { получить; набор; }    }    // Класс Pool - самый важный класс в шаблоне проектирования пула объектов. Он контролирует доступ к    // объединение объектов в пул, поддерживающее список доступных объектов и коллекцию объектов, которые уже были    // запрошены из пула и все еще используются. Пул также гарантирует, что освобожденные объекты    // возвращаются в подходящее состояние, готовые к следующему запросу.     общественный статический класс Бассейн    {        частный статический Список<PooledObject> _имеется в наличии = новый Список<PooledObject>();        частный статический Список<PooledObject> _в использовании = новый Список<PooledObject>();         общественный статический PooledObject GetObject()        {            замок(_имеется в наличии)            {                если (_имеется в наличии.Считать != 0)                {                    PooledObject po = _имеется в наличии[0];                    _в использовании.Добавить(po);                    _имеется в наличии.RemoveAt(0);                    вернуть po;                }                еще                {                    PooledObject po = новый PooledObject();                    _в использовании.Добавить(po);                    вернуть po;                }            }        }         общественный статический пустота ReleaseObject(PooledObject po)        {            Очистка(po);             замок (_имеется в наличии)            {                _имеется в наличии.Добавить(po);                _в использовании.удалять(po);            }        }         частный статический пустота Очистка(PooledObject po)        {            po.TempData = значение NULL;        }    }}

В приведенном выше коде PooledObject включает два свойства. Один хранит время, когда объект был впервые создан. Другой содержит строку, которая может быть изменена клиентом, но сбрасывается, когда объект PooledObject возвращается в пул. Это показывает процесс очистки при освобождении объекта, который гарантирует, что он находится в допустимом состоянии, прежде чем его можно будет снова запросить из пула.

Ява

Java поддерживает пул потоков через java.util.concurrent.ExecutorService и другие родственные классы. У службы исполнителя есть определенное количество «базовых» потоков, которые никогда не отбрасываются. Если все потоки заняты, служба выделяет разрешенное количество дополнительных потоков, которые позже отбрасываются, если не используются в течение определенного времени истечения срока. Если больше нет разрешенных потоков, задачи могут быть помещены в очередь. Наконец, если эта очередь может стать слишком длинной, ее можно настроить для приостановки запрашивающего потока.

общественный класс PooledObject {	общественный Строка temp1;	общественный Строка temp2;	общественный Строка temp3;		общественный Строка getTemp1() {		вернуть temp1;	}	общественный пустота setTemp1(Строка temp1) {		этот.temp1 = temp1;	}	общественный Строка getTemp2() {		вернуть temp2;	}	общественный пустота setTemp2(Строка temp2) {		этот.temp2 = temp2;	}	общественный Строка getTemp3() {		вернуть temp3;	}	общественный пустота setTemp3(Строка temp3) {		этот.temp3 = temp3;	}}
общественный класс PooledObjectPool {	частный статический длинная expTime = 6000;// 6 секунд	общественный статический HashMap<PooledObject, Длинная> имеется в наличии = новый HashMap<PooledObject, Длинная>();	общественный статический HashMap<PooledObject, Длинная> в использовании = новый HashMap<PooledObject, Длинная>();		общественный синхронизированный статический PooledObject getObject() {		длинная сейчас же = Система.currentTimeMillis();		если (!имеется в наличии.пусто()) {			для (карта.Вход<PooledObject, Длинная> вход : имеется в наличии.entrySet()) {				если (сейчас же - вход.getValue() > expTime) { // срок действия объекта истек					popElement(имеется в наличии);				} еще {					PooledObject po = popElement(имеется в наличии, вход.getKey());					От себя(в использовании, po, сейчас же); 					вернуть po;				}			}		}		// либо PooledObject недоступен, либо срок действия каждого истек, поэтому верните новый		вернуть createPooledObject(сейчас же);	}			частный синхронизированный статический PooledObject createPooledObject(длинная сейчас же) {		PooledObject po = новый PooledObject();		От себя(в использовании, po, сейчас же);		вернуть po;        }	частный синхронизированный статический пустота От себя(HashMap<PooledObject, Длинная> карта,			PooledObject po, длинная сейчас же) {		карта.положил(po, сейчас же);	}	общественный статический пустота releaseObject(PooledObject po) {		cleanUp(po);		имеется в наличии.положил(po, Система.currentTimeMillis());		в использовании.удалять(po);	}		частный статический PooledObject popElement(HashMap<PooledObject, Длинная> карта) {		 карта.Вход<PooledObject, Длинная> вход = карта.entrySet().итератор().Следующий();		 PooledObject ключ= вход.getKey();		 // Длинное значение = entry.getValue ();		 карта.удалять(вход.getKey());		 вернуть ключ;	}		частный статический PooledObject popElement(HashMap<PooledObject, Длинная> карта, PooledObject ключ) {		карта.удалять(ключ);		вернуть ключ;	}		общественный статический пустота cleanUp(PooledObject po) {		po.setTemp1(значение NULL);		po.setTemp2(значение NULL);		po.setTemp3(значение NULL);	}}

Смотрите также

Заметки

  1. ^ а б Гетц, Брайан (27 сентября 2005 г.). "Теория и практика Java: новые легенды о городской производительности". IBM developerWorks. Архивировано из оригинал на 2005-09-27. Получено 2012-08-28.

использованная литература

внешние ссылки