BDE 4.14.0 Production release
Loading...
Searching...
No Matches
bdlcc_objectpool

Detailed Description

Outline

Purpose

Provide a thread-safe object pool.

Classes

See also
bdlcc_sharedobjectpool

Description

This component provides a generic thread-safe pool of objects, bdlcc::ObjectPool, using the acquire-release idiom and a struct with useful functors for a pool of objects, bdlcc::ObjectPoolFunctors. An object pool provides two main methods: getObject, which returns an object from the pool, and releaseObject, which returns an object to the pool for further reuse (thus avoiding the overhead of object construction and destruction). A major requirement of using the object pool is that any call to getObject can be satisfied by any object in the pool.

Thread Safety

The bdlcc::ObjectPool class template is fully thread-safe (see {bsldoc_glossary |Fully Thread-Safe}), assuming that the allocator is fully thread-safe. Each method is executed by the calling thread.

Object Construction and Destruction

The object pool owns the memory required to store the pooled objects, and manages the construction, resetting, and destruction of objects. The user may supply functors to create objects and to reset them to a valid state for their return to the pool. Alternatively, this component supplies reasonable defaults. Upon destruction, the object pool deallocates all memory associated with the objects in the pool.

The object pool also implements the bdlma::Factory protocol for TYPE. Its createObject and deleteObject methods are provided only for this purpose and should not be invoked directly (they are just synonyms for getObject and releaseObject, respectively). The pool can thus be used anywhere a bdlma::Factory (or, therefore, a bdlma::Deleter) is expected.

Integrating with bslma::ManagedPtr and bsl::shared_ptr

A bdlcc::ObjectPool is designed to work with both managed and shared pointer types. Note however, that bdlcc_sharedobjectpool is an object-pool specifically designed for use with shared pointers.

Because bdlcc::ObjectPool provides a deleteObject method, it can serve as a factory of both bslma::ManagedPtr and bsl::shared_ptr objects. For example, to create a managed pointer from an object pool of bsl::string objects:

bslma::ManagedPtr<bsl::string> managedPtr(pool.getObject(), &pool);
Definition bdlcc_objectpool.h:686
TYPE * getObject()
Definition bdlcc_objectpool.h:1214
Definition bslma_managedptr.h:1182

To create a shared pointer (using the same object pool):

bsl::shared_ptr<bsl::string> sharedPtr(pool.getObject(), &pool, allocator);
Definition bslstl_sharedptr.h:1830
Definition bslma_allocator.h:457
static Allocator * allocator(Allocator *basicAllocator=0)
Definition bslma_default.h:897

Note that an allocator is a required argument to the bsl::shared_ptr constructor used here, and the provided allocator is used to supply memory for the internal representation of the pointer, and not to allocate memory for the object itself.

Creator and Resetter Template Contract

bdlcc::ObjectPool is templated on two types CREATOR and RESETTER in addition to the underlying object TYPE. Objects of these types may be provided at construction. The namespace bdlcc::ObjectPoolFunctors provides several commonly used implementations. The creator will be invoked as: void(*)(void*, bslma::Allocator*). The resetter will be invoked as: void(*)(TYPE*). The creator functor is called to construct a new object of the parameterized TYPE when the pool must be expanded (and thus it typically invokes placement new and passes its allocator argument to the constructor of TYPE). The resetter functor is called before each object is returned to the pool, and is required to put the object into a state such that it is ready to be reused. The defaults for these types are as follows:

Definition bdlcc_objectpool.h:428
Forward declaration.
Definition bslstl_function.h:934

bdlcc::ObjectPoolFunctors::Nil is a no-op; it is only suitable if the objects stored in the pool are always in a valid state to be reused. Otherwise another kind of RESETTER should be provided. In bdlcc::ObjectPoolFunctors, the classes Clear, RemoveAll, and Reset are all acceptable types for RESETTER. Since these functor types are fully inlined, it is generally most efficient to define reset (or clear or removeAll) in the underlying TYPE and allow the functor to call that method. The CREATOR functor defaults to an object that invokes the default constructor with placement new, passing the allocator argument if the type traits of the object indicate it uses an allocator (see bslalg_typetraits ). If a custom creator functor or a custom CREATOR type is specified, it is the user's responsibility to ensure that it correctly passes its allocator argument to the constructor of TYPE if TYPE takes an allocator.

Exception safety

There are two potential sources of exceptions in this component: memory allocation and object construction. The object pool is exception-neutral with full guarantee of rollback for the following methods: if an exception is thrown in getObject, reserveCapacity, or increaseCapacity, then the pool is in a valid unmodified state (i.e., identical to its state prior to the call to getObject). No other method of bdlcc::ObjectPool can throw.

Pool replenishment policy

The growBy parameter can be specified in the pool's constructor to instruct the pool how to increase its capacity each time the pool is depleted. If growBy is positive, the pool always replenishes itself with enough objects to satisfy at least growBy object requests before the next replenishment. If growBy is negative, the pool will increase its capacity geometrically until it exceeds the internal maximum (which is implementation-defined), and after that it will be replenished with constant number of objects. If growBy is not specified, it defaults to -1 (i.e., geometric increase beginning at 1).

Usage

This section illustrates intended use of this component.

Example 1: Handling Database Queries

In this example, we simulate a database server accepting queries from clients and executing each query in a separate thread. Client requests are simulated by function getClientQuery which returns a query to be executed. The class Query encapsulates a database query and queryFactory is an object of a query factory class QueryFactory.

enum {
k_CONNECTION_OPEN_TIME = 100, // (simulated) time to open a
// connection (in microseconds)
k_CONNECTION_CLOSE_TIME = 8, // (simulated) time to close a
// connection (in microseconds)
k_QUERY_EXECUTION_TIME = 4 // (simulated) time to execute a query
// (in microseconds)
};
/// This class simulates a database connection.
class my_DatabaseConnection
{
public:
my_DatabaseConnection()
{
bslmt::ThreadUtil::microSleep(k_CONNECTION_OPEN_TIME);
}
~my_DatabaseConnection()
{
bslmt::ThreadUtil::microSleep(k_CONNECTION_CLOSE_TIME);
}
void executeQuery(Query *query)
{
bslmt::ThreadUtil::microSleep(k_QUERY_EXECUTION_TIME);
(void)query;
}
};
static void microSleep(int microseconds, int seconds=0)
Definition bslmt_threadutil.h:955

The server runs several threads which, on each iteration, obtain a new client request from the query factory, and process it, until the desired total number of requests is achieved.

extern "C" void serverThread(bsls::AtomicInt *queries,
int max,
void (*queryHandler)(Query*))
{
while (++(*queries) <= max) {
Query *query = queryFactory->createQuery();
queryHandler(query);
}
}
Definition bsls_atomic.h:743

We first give an implementation that does not uses the object pool. Later we will give an implementation using an object pool to manage the database connections. We also keep track of total response time for each case. When object pool is not used, each thread, in order to execute a query, creates a new database connection, calls its executeQuery method to execute the query and finally closes the connection.

/// Handle the specified `query` without using an objectpool.
extern "C" void queryHandler1(Query *query)
{
my_DatabaseConnection connection;
connection.executeQuery(query);
totalResponseTime1 += t2 - t1;
queryFactory->destroyQuery(query);
// `connection` is implicitly destroyed on function return.
}
static Types::Int64 getTimer()
long long Int64
Definition bsls_types.h:132

The main thread starts and joins these threads:

enum {
k_NUM_THREADS = 8,
k_NUM_QUERIES = 10000
};
bsls::AtomicInt numQueries(0);
&numQueries,
static_cast<int>(k_NUM_QUERIES),
&queryHandler1),
k_NUM_THREADS);
tg.joinAll();
static Bind< bslmf::Nil, t_FUNC, Bind_BoundTuple0 > bind(t_FUNC func)
Definition bdlf_bind.h:1830
Definition bslmt_threadgroup.h:156
int addThreads(const INVOKABLE &functor, int numThreads)
Definition bslmt_threadgroup.h:247

In above strategy, clients always incur the delay associated with opening and closing a database connection. Now we show an implementation that will use object pool to pool the database connections.

Object Pool Creation and Functor Argument

In order to create an object pool, we may specify, at construction time, a functor encapsulating object creation. The pool invokes this functor to create an object in a memory location supplied by the allocator specified at construction and owned by the pool. By default, the creator invokes the default constructor of the underlying type, passing the pool's allocator if the type uses the bslma::Allocator protocol to supply memory (as specified by the "Uses Bslma Allocator" trait, see bslalg_typetraits ). If this behavior is not sufficient, we can supply our own functor for type creation.

Creating an Object Pool that Constructs Default Objects

When the default constructor of our type is sufficient, whether or not that type uses bslma::Allocator, we can simply use the default behavior of bdlcc::ObjectPool:

Creating an Object Pool that Constructs Non-Default Objects

In this example, if we decide that connection IDs must be supplied to objects allocated from the pool, we must define a function which invokes placement new appropriately. When using a custom creator functor, it is the responsibility of client code to pass the pool's allocator (supplied as the second argument to the functor) to the new object if it uses bslma::Allocator.

void createConnection(void *arena, bslma::Allocator *alloc, int id)
{
new (arena) my_DatabaseConnection(id, alloc);
}

then...

int myId = 100;
bdlf::BindUtil::bind(&createConnection,
myId));
const PlaceHolder< 1 > _1
const PlaceHolder< 2 > _2

Whichever creator we choose, the modified server looks like

connectionPool = &pool;
for (int i = 0; i < k_NUM_QUERIES; ++i) {
my_Query *query = getClientQuery();
bslmt::ThreadUtil::create(&threads[i], queryHandler2, (void *)query);
}
for (int i = 0; i < k_NUM_QUERIES; ++i) {
}
static int create(Handle *handle, ThreadFunction function, void *userData)
Definition bslmt_threadutil.h:813
static int join(Handle &threadHandle, void **status=0)
Definition bslmt_threadutil.h:949

Modified queryHandler

Now each thread, instead of creating a new connection, gets a connection from the object pool. After using the connection, the client returns it back to the pool for further reuse. The modified queryHandler is following.

/// Process the specified `query`.
void queryHandler2(Query *query)
{
my_DatabaseConnection *connection = connectionPool->getObject();
connection->executeQuery(query);
totalResponseTime2 += t2 - t1;
connectionPool->releaseObject(connection);
queryFactory->destroyQuery(query);
}
void releaseObject(TYPE *object)
Definition bdlcc_objectpool.h:1313

The total response time for each strategy is:

totalResponseTime1 = 199970775520
totalResponseTime2 = 100354490480