Введение

Статья об общем пуле объектов.

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

Описание

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

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

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

Реализация

Объектные пулы могут быть реализованы автоматически в языках, таких как C++, с помощью умных указателей. В конструкторе умного указателя можно запросить объект из пула, а в деструкторе умного указателя – вернуть объект обратно в пул. В языках со сборкой мусора, где отсутствуют деструкторы (гарантированно вызываемые при разворачивании стека), объектные пулы должны быть реализованы вручную, путем явного запроса объекта у фабрики и возврата объекта посредством вызова метода высвобождения (как в шаблоне высвобождения). Использовать финализатор для этого не рекомендуется, поскольку обычно нет гарантий относительно времени (или даже факта) его выполнения. Вместо этого следует использовать блок "try-finally", чтобы обеспечить нейтральность операций получения и освобождения объекта к исключениям. Ручные объектные пулы просты в реализации, но сложнее в использовании, так как требуют ручного управления памятью объектов пула.

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

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

Недостатки

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

Критика

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