Шаблон пула объектов

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

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

Описание

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

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

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

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

Преимущества

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

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

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

Выполнение

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

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

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

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

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

Подводные камни

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

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

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

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

Критика

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

Примеры

Идти

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

// пул пакетов пул пакетов импорт ( "ошибки" "журнал" "математика/rand" "синхронизация" "время" ) const getResMaxTime = 3 * время . Секунда     var ( ErrPoolNotExist = ошибки . New ( "пул не существует" ) ErrGetResTimeout = ошибки . New ( "получить время ожидания ресурса" ) )     // Тип ресурса Структура ресурса { resId int }    //NewResource Имитация медленной инициализации создания ресурса // (например, TCP-соединение, получение симметричного ключа SSL, аутентификация auth занимают много времени ) func NewResource ( id int ) * Resource { time.Sleep ( 500 * time.Millisecond ) return & Resource { resId : id } }        // Ресурсы моделирования занимают много времени, а случайное потребление составляет 0~400 мс func ( r * Resource ) Do ( workId int ) { time.Sleep ( time.Duration ( rand.Intn ( 5 ) ) * 100 * time.Millisecond ) log.Printf ( " using resource # % d done work % d finish\n " , r.resId , workId ) }           //Пул на основе реализации канала Go, чтобы избежать проблемы состояния гонки ресурсов type Pool chan * Resource   // Создаем пул ресурсов указанного размера // Ресурсы создаются одновременно для экономии времени инициализации ресурсов func New ( size int ) Pool { p := make ( Pool , size ) wg := new ( sync.WaitGroup ) wg.Add ( size ) for i : = 0 ; i < size ; i ++ { go func ( resId int ) { p < - NewResource ( resId ) wg.Done ( ) } ( i ) } wg.Wait ( ) return p }                       //GetResource на основе канала, состояние гонки ресурсов предотвращается, и для пустого пула устанавливается тайм-аут получения ресурса func ( p Pool ) GetResource () ( r * Resource , err error ) { select { case r := <- p : return r , nil case <- time . After ( getResMaxTime ): return nil , ErrGetResTimeout } }                 //GiveBackResource возвращает ресурсы в пул ресурсов func ( p Pool ) GiveBackResource ( r * Resource ) error { if p == nil { return ErrPoolNotExist } p <- r return nil }              // основной пакет основной пакет импорт ( "github.com/tkstorm/go-design/creational/object-pool/pool" "log" "sync" ) func main () { // Инициализируем пул из пяти ресурсов, // который можно изменить до 1 или 10, чтобы увидеть разницу size := 5 p := pool . New ( size )      // Вызывает ресурс для выполнения задания id doWork := func ( workId int , wg * sync . WaitGroup ) { defer wg . Done () // Получает ресурс из пула ресурсов res , err := p . GetResource () if err != nil { log . Println ( err ) return } // Ресурсы для возврата defer p . GiveBackResource ( res ) // Использует ресурсы для обработки работы res . Do ( workId ) }               // Моделируем 100 параллельных процессов для получения ресурсов из пула активов num := 100 wg : = new ( sync.WaitGroup ) wg.Add ( num ) for i : = 0 ; i < num ; i ++ { go doWork ( i , wg ) } wg.Wait ( ) }              

С#

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

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

пространство имен DesignPattern.Objectpool ; // Класс PooledObject — это тип, создание экземпляра которого требует больших затрат или занимает много времени, // или который имеет ограниченную доступность, поэтому должен храниться в пуле объектов. public class PooledObject { private DateTime _createdAt = DateTime . Now ;        public DateTime CreatedAt => _createdAt ;     публичная строка TempData { получить ; установить ; } }      // Класс Pool управляет доступом к объединенным объектам. Он поддерживает список доступных объектов и // коллекцию объектов, полученных из пула и используемых. Пул гарантирует, что освобожденные объекты // возвращаются в подходящее состояние, готовое к повторному использованию. public static class Pool { private static List < PooledObject > _available = new List < PooledObject > (); private static List < PooledObject > _inUse = new List < PooledObject > ();                  public static PooledObject GetObject ( ) { lock ( _available ) { if ( _available.Count ! = 0 ) { PooledObject po = _available [ 0 ] ; _inUse.Add ( po ) ; _available.RemoveAt ( 0 ) ; return po ; } else { PooledObject po = new PooledObject ( ) ; _inUse.Add ( po ) ; return po ; } } }                                   public static void ReleaseObject ( PooledObject po ) { CleanUp ( po );       блокировка ( _доступно ) { _доступно . Добавить ( po ); _в использовании . Удалить ( po ); } }       private static void CleanUp ( PooledObject po ) { po . TempData = null ; } }         

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

Ява

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

public class PooledObject { public String temp1 ; public String temp2 ; public String temp3 ; public String getTemp1 ( ) { return temp1 ; } public void setTemp1 ( String temp1 ) { this.temp1 = temp1 ; } public String getTemp2 ( ) { return temp2 ; } public void setTemp2 ( String temp2 ) { this.temp2 = temp2 ; } public String getTemp3 ( ) { return temp3 ; } public void setTemp3 ( String temp3 ) { this.temp3 = temp3 ; } }                                       
public class PooledObjectPool { private static long expTime = 6000 ; //6 секунд public static HashMap < PooledObject , Long > available = new HashMap < PooledObject , Long > (); public static HashMap < PooledObject , Long > inUse = new HashMap < PooledObject , Long > (); public synchronized static PooledObject getObject () { long now = System . currentTimeMillis (); if ( ! available . isEmpty ()) { for ( Map . Entry < PooledObject , Long > entry : available . entrySet ()) { if ( now - entry . getValue () > expTime ) { // объект просрочен popElement ( available ); } else { PooledObject po = popElement ( available , entry . getKey ()); push ( inUse , po , now ); return po ; } } }                                                         // либо PooledObject недоступен, либо срок действия каждого истек, поэтому возвращаем новый return createPooledObject ( now ); } private synchronized static PooledObject createPooledObject ( long now ) { PooledObject po = new PooledObject (); push ( inUse , po , now ); return po ; }               private synchronized static void push ( HashMap < PooledObject , Long > map , PooledObject po , long now ) { map . put ( po , now ); }           public static void releaseObject ( PooledObjectpo ) { cleanUp ( po ); available.put ( po , System.currentTimeMillis ( )) ; inUse.remove ( po ) ; } private static PooledObject popElement ( HashMap < PooledObject , Long > map ) { Map.Entry < PooledObject , Long > entry = map.entrySet ( ) . iterator ( ) . next ( ) ; PooledObject key = entry.getKey ( ) ; // Длинное значение = entry.getValue ( ) ; map.remove ( entry.getKey ( ) ) ; возврат ключа ; } private static PooledObject popElement ( HashMap < PooledObject , Long > map , PooledObject key ) { map.remove ( key ) ; возврат ключа ; } public static void cleanUp ( PooledObjectpo ) { po.setTemp1 ( null ) ; po.setTemp2 ( null ) ; po . setTemp3 ( null ); } }                                      

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

Примечания

  1. ^ ab Goetz, Brian (2005-09-27). "Теория и практика Java: Городские легенды производительности, пересмотр". IBM . IBM developerWorks. Архивировано из оригинала 2012-02-14 . Получено 2021-03-15 .

Ссылки

  • Кирхер, Михаэль; Прашант Джейн (2002-07-04). "Pooling Pattern" (PDF) . EuroPLoP 2002 . Германия . Получено 2007-06-09 .
  • Гольдштейн, Саша; Зурбалев, Дима; Флатов, Идо (2012). Pro .NET Performance: Optimize Your C# Applications. Apress. ISBN 978-1-4302-4458-5.
  • Статья OODesign
  • Повышение производительности с помощью пула объектов (Microsoft Developer Network)
  • Статья на Developer.com
  • Запись в репозитории шаблонов Портленда
  • Apache Commons Pool: мини-фреймворк для корректной реализации пула объектов в Java
  • Шаблоны программирования игр: Object Pool
Получено с "https://en.wikipedia.org/w/index.php?title=Object_pool_pattern&oldid=1163175488"