Card image cap

Exploring Design Patterns - Object Pool

Design Patterns  • Posted 17 days ago

While developing applications, resource management is one of the basic performance parameters we need to look at. Since memory can be a limited resource, creating and deallocating objects over time in an application can result in a performance hit. Things get even worse when we're dealing with heavy objects for which the initialization cost is high.

What is an Object Pool?

ObjectPool is one of the 23 GoF (Gang of Four) design patterns which address frequently occuring design problems during an application development. It is a Creational Pattern which offers solutions for better object creations and management.

ObjectPool pattern addresses the above said frequent object creation issue by using a set of initialized objects ready in an "object pool" and lets the clients which require that "pull" an object from the "pool" and release it back to the pool once its done with the object. This reduces the need for creating objects when required and hence can avoid the initialization cost for such scenarios.

Generally, ObjectPools maintain a collection of the available objects along with a collection of objects in use. Whenever an object is requested, the Pool checks for an available object and if available returns the object and places its reference in the used counter. Once that object is released by the client it drops the reference of that object from its used counter and places the object back into available collection.

However, when the pool is out of available objects to serve for a requesting client, it employs different strategies to handle the scenario, such as:

  • Throws an error that there's no available object
  • Expands the Pool size - creates a new object and returns to the client
  • Blocks the client until an object is released by another client

An ObjectPool is completely responsible for "resetting" the state of the object when it is returned by the client before marking it available for other clients to use it.

"An ObjectPool is implemented as a Singleton class."

Advantages of using a Object Pool

  • Significantly increases the application performance since the initialization cost is now eliminated
  • Provides a neat mechanism of reusing objects among the requesting clients, while it being responsible for the object management
  • Helpful in scenarios where objects are created frequently but are used for a shorter lifespan
  • Advantageous in cases of objects which have a variable creation time such as database connections, socket connections, threads and so on

Things to keep in Mind

While using an ObjectPool is quite useful, developers must keep in mind that:

  • Implementing an ObjectPool manually means that the Pool is now responsible for keeping track of the objects which are currently live and which are to be disposed.
  • In modern programming languages where the object allocation happens quite faster, such classic object pool designs might consume more resources.
  • Also, ObjectPools must be created and used only in scenarios where frequent object creations happen; in cases where the pool maintains a set of objects which are not frequently accessed can increase the occupied memory, which can become an issue instead of advantage.
  • In cases of simple object pooling, where the objects hold only memory and not any external resources (such as sockets, database connections and so on), this may not be an efficient solution.

Implementing an Object Pool - C# and .NET Core DI

In a simple implementation of an ObjectPool, we have an interface which declares two methods for any of its implementation: a GetObject() method which returns an object from the pool, and a Release() method which passes the object which is to be reused.

public interface IObjectPool<T> 
{
    T GetObject();
    void Release(T o);
}

The ReaderObjectPool implements this interface for maintaining a "pool" of reusable Reader instances. The GetObject() method employs an "elastic pool" approach, where the size of the pool increases when the pool runs out of Reader instances.

public class ReaderObjectPool : IObjectPool<Reader>
{
    private List<Reader> _available, _used;

    public ReaderObjectPool()
    {
        _available = new List<Reader>();
        _used = new List<Reader>();
    }

    public Reader GetObject()
    {
        lock (_available)
        {
            if (_available.Count > 0)
            {
                Reader reader = _available[0];
                _available.RemoveAt(0);
                _used.Add(reader);
                return reader;
            }
            else
            {
                Reader reader = new Reader();
                _used.Add(reader);
                return reader;
            }
        }
    }

    public void Release(Reader reader)
    {
        lock (_available)
        {
          _used.Remove(reader);
          _available.Add(reader);
        }
    }
}

The Reader object is assumed to be a complex one, which takes a high initialization cost.

public class Reader
{
    public Guid Id { get; set; }
    public DateTime CreatedOn { get; set; }

    public Reader()
    {
        this.CreatedOn = DateTime.Now;
        this.Id = Guid.NewGuid();
    }

    public void DoSomeComplexProcess()
    {
        Console.WriteLine($"This is Reader# {Id} Created on ${CreatedOn.ToString()}");
    }
}

In case of a DI container approach, the ObjectPool is marked as a Singleton so as to have a single global pool of resources across the application.

services.AddSingleton<IObjectPool<Reader>, ReaderObjectPool>();

or it is implemented separately as a Singleton:

public sealed class ReaderObjectPoolSingleton : IObjectPool<Reader>
{
    private static ReaderObjectPoolSingleton instance;

    // a private constructor
    private ReaderObjectPoolSingleton()
    {
    }
    
    public static ReaderObjectPoolSingleton GetInstance()
    {
        if (instance == null)
        {
            instance = new ReaderObjectPoolSingleton();
        }
        
        return instance;
    }

    public Reader GetObject()
    {
        // implementation
    }

    public void Release(Reader o)
    {
        // implementation
    }
}

At the client end, the ReaderObjectPool is used to get an object of type Reader and some operation happens. Finally once the need is over, the pulled instance is released back to the pool.

public class Client 
{
    private readonly IObjectPool<Reader> _pool;

    public Client(IObjectPool<Reader> readerPool)
    {
        _pool = readerPool;
    }

    public void DoProcess()
    {
        // Fetch a Reader from the Pool
        var reader = _pool.GetObject();

        // Do Something with the Reader fetched
        reader.DoSomeComplexProcess();

        // Release the Reader to the Pool 
        // so that others can use it
        _pool.Release(reader);
                
        return Ok();
    }
}

The output when this application is executed multiple times indicate that the same object from the pool has been reused for all consequent operations.

This is Reader# 9bea42ea-e383-4b6f-9977-374fab2e7838 Created on $18-07-2020 10:44:49 AM
This is Reader# 9bea42ea-e383-4b6f-9977-374fab2e7838 Created on $18-07-2020 10:44:49 AM

object pool design pattern example object pool design pattern c# example object pool design object pool design pattern creational what is object pool design pattern design pattern object pool when to use object pool design pattern object pooling design pattern

We use cookies to provide you with a great user experience, analyze traffic and serve targeted promotions.   Learn More   Accept