Abstract: Semaphores are used to coordinate multiple threads and processes. That semaphores provide multiple threads with simultaneous access to a shared resource is highlighted by the TFixedConnectionPool class described in this article.

Semaphores are like mutexes on steroids. Not only do they provide for blocking thread synchronization, but they permit two or more threads, up to some specified maximum number, to work concurrently with a shared resource. As a result, they are unlike most other synchronization objects, which typically provide access to one thread at a time. For an introduction to the creation and use of semaphores, read the preceding article in this series on the Borland Developer Network site by clicking here.

This unique feature of a semaphore, the ability to provide two or more threads with simultaneous access to a resource, lends itself very well to the implementation of objects that contain a fixed number of resources that need to be made available in a multithreaded environment. Two common examples of objects of this type are thread pools and database connection pools. This article discusses the use of a semaphore to implement a connection pool.

Connection Pool Overview

A connection pool is an object designed to provide thread-safe access to a limited number of database connections in a multithreaded environment. In most scenarios, a connection pool contains one or more active database connections that can be used by any thread needing to work with the database.

A connection pool provides two primary benefits. The first is related to performance. Making and dropping a connection to a database is a relatively time consuming process. Depending on the environment, if each thread must make and then drop a connection each time data access is needed, a significant amount of processing power will be spent on the connection process. By comparison, a connection pool includes a fixed number of connections that can be shared among many threads. When a thread needs to work with a database, it obtains a handle to an existing connection that it uses temporarily. This reuse can dramatically reduce the number of times a connection is made and dropped, thereby producing an overall increase in performance.

The second benefit is related to connection counts. Many database licensing schemes permit only a limited number of simultaneous connections to the database. A connection pool, therefore, becomes a convenient mechanism for ensuring that no more than some fixed maximum number of connections can be active at a given moment. While a semaphore can be used outside a connection pool to manage the maximum number of simultaneous connections, a connection pool automatically provides this feature with the addition benefit of increased performance.

The FixedConnectionPool

The fixed connection pool unit, available for download from Code Central (by clicking here), provides an implementation of a connection pool. The purpose of this code is to demonstrate the use of a semaphore to provide multithreaded access to a limited resource (the connections in the pool). If you want to use this code in a production application, it is your responsibility to certify that the connection pool meets your application's requirements. Note also, as of this writing, this connection pool has not been tested on a multiprocessor system.

There are four type declarations used in this connection pool. One is an interface and three are classes. The interface, named IConnection, defines the formal declaration of the connection pool interface, and also provides for the lifecycle management of individual connections.

The three classes consist of the connection pool object itself, named TFixedConnectionPool, as well as two helper classes. These helper classes are TConnectionModule, a data module descendant that is created once for each open connection, and TCleanupThread, a TThread descendant that drops connections that have not been used after some specified period of time.

IConnection

Let's begin by considering the IConnection interface. This interface declaration is shown here:

IConnection = Interface(IInterface)
//CHANGE
//To use a connection of another type, change the
//return type of the Connection function
function Connection: TSQLConnection;
function GetRefCount: Integer;
function GetLastAccess: TDateTime;
property LastAccess: TDateTime read GetLastAccess;
property RefCount: Integer read GetRefCount;
end;

The Connection function returns a SQLConnection reference, which when implemented, refers to a SQLConnection object that is actively connected to a database. If you want to modify this connection pool to use a data access technology other than dbExpress, you need to change the return type of this Connection function to some other connection type, such as an TADOConnection (ADO), TIBDatabase (Interbase Express), or TSession (BDE).

The functions GetRefCount and GetLastAccess are the accessor methods for the RefCount and LastAccess properties, respectively. RefCount returns the number of current references to the IConnection implementing object. LastAccess returns the TDateTime of the last release of the IConnection implementing object. This value is used by the cleanup thread object to test whether the IConnection implementing object has gone unused for an extended period of time, and if so, to drop the unused connection.

TConnectionModule

The actual database connection is provided for by the TConnectionModule class. This class is a TDataModule descendant, and the connection component appears on this data module, as shown in the following figure.

TConnectionModule implements the IConnection interface. The SQLConnection that appears on this data module is the reference that is returned when the Connection method is invoked. As mentioned in the preceding discussion of the IConnection interface, if you want to use a connection other than dbExpress, you will replace this SQLConnection component with one that provides a connection using an alternative data access mechanism.

The following is the declaration of the TConnectionModule class. This class is commented extensively, and I am leaving these comments in this code segment for clarity.

//This data module provides the implementation
//of the IConnection interface. To use a data access
//mechanism other than dbExpress, modify the components
//that appear on this data module, and change the class
//of the Connection function in the IConnection interface
//as well as in this class.
TConnectionModule = class(TDataModule, IConnection)
SQLConnection1: TSQLConnection;
private
{ Private declarations }
protected
FRefCount: Integer;
FLastAccess: TDateTime;
//When the data module is created the
//connection pool that creates the data module
//will assign its critical section to this field.
//The data module will use this critical section
//to synchronize access to its reference count.
CriticalSection: TCriticalSection;
//This semaphore points to the FixedConnectionPool's
//semaphore. It will be used to call ReleaseSemaphore
//from the _Release method of the TDataModule.
Semaphore: THandle;
//These two static methods are reintroduced
//in order to implement lifecycle management
//for the interface of this object.
//Normally, unlike normal COM objects, Delphi
//TComponent descendants are not lifecycle managed
//when used in interface references.
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
{IConnection methods}
function GetLastAccess: TDateTime;
function GetRefCount: Integer;
public
{ Public declarations }
{IConnection method}
//CHANGE
//To use a connection of another type, change the
//return type of the Connection function
function Connection: TSQLConnection;
end;

The Connection, GetLastAccess, and GetRefCount methods are implemented in this class to satisfy the implementation of the IConnection interface. The _AddRef and _Release static methods are reintroduced in this class to implement lifecycle management on the interface. This is necessary since the _AddRef and _Release methods introduced in TComponent (a TDataModule ancestor), do not implement lifecycle management through reference counting for VCL (visual component library) objects. This lifecycle management plays a critical role in the management of the connection pool.

The TConnectionModule class also contains two member fields, CriticalSection and Semaphore. Both of these members are used to point to a TCriticalSection and a semaphore (a THandle), both of which are maintained by the TFixedConnectionPool class (and are assigned to these members by the TFixedConnectionPool instance when it creates an instance of the connection module).

The critical section class is used to synchronize access to the internal reference count of the TConnectionPool instances, and the semaphore controls access to instances of the TConnectionModule class. This is shown in the _AddRef and _Release method implementations, shown here.

function TConnectionModule._AddRef: Integer;
begin
//increment the reference count
CriticalSection.Enter;
try
Inc(FRefCount);
Result := FRefCount;
finally
CriticalSection.Leave;
end;
end; function TConnectionModule._Release: Integer;
begin
//decrement the reference count
CriticalSection.Enter;
try
Dec(FRefCount);
Result := FRefCount;
//if not more references, call Destroy
if Result = 0 then
Destroy
else
Self.FLastAccess := Now;
finally
CriticalSection.Leave;
if FRefCount = 1 then
ReleaseSemaphore(Semaphore, 1, nil);
end;
end;

Notice that the critical section is locked before the internal reference count can be incremented or decremented. Also, notice that if the reference count of a TConnectionModule instance drops to one that ReleaseSemaphore is called. When the internal reference count on a TConnectionModule instance drops to one, the thread that was using the connection has released it, and the only remaining reference to the connection module is held by the connection pool itself.

TFixedConnectionPool

The connection pool is implemented by the TFixedConnectionPool class, shown in the following code segment. Once again, the extensive comments have been left in the code for clarity.

//This is the class that manages the connection pool
TFixedConnectionPool = class(TObject)
private
FPool: array of IConnection;
FPoolSize: Integer;
FTimeout: LargeInt;
CleanupThread: TCleanupThread;
//This semaphore is used to limit the number of
//simultaneous connections. When the nth+1 connection
//is requested, it will be blocked until a connection
//becomes available.
Semaphore: THandle;
//This is the critical section that synchronizes
//access to the connection module reference counts
CriticalSection: TCriticalSection;
public
//This overloaded constructor takes two optional
//parameters. These parameters specify the size
//of the connection pool, as well as how long idle
//connections in the connection pool will be kept.
constructor Create(const PoolSize: Integer = 10;
const CleanupDelayMinutes: Integer = 5;
const Timeoutms: LargeInt = 10000); overload;
destructor Destroy; override;
//This function returns an object
//that implements the IConnection interface.
//This object can be a data module, as was
//done in this example.
function GetConnection: IConnection;
end;

One of the more notable elements of this declaration is the FPool member, which is a dynamic array of IConnection references. This array provides the list of pointers to the connections in the connection pool.

The connection pool is configured in the overloaded Create constructor (Create is static in TObject, the TConnectionPool ancestor, which is why it is not overridden). The constructor is shown here.

constructor TFixedConnectionPool.Create(const PoolSize: Integer = 10;
const CleanupDelayMinutes: Integer = 5;
const Timeoutms: LargeInt = 10000);
begin
FPoolSize := PoolSize;
FTimeout := Timeoutms;
Semaphore := CreateSemaphore(nil, PoolSize, PoolSize, '');
CriticalSection := TCriticalSection.Create;
//Set the length of the connection pool
SetLength(FPool, PoolSize);
//Create and start the cleanup thread
CleanupThread := TCleanupThread.Create(True,
CleanupDelayMinutes);
with CleanupThread do
begin
FreeOnTerminate := True;
Priority := tpLower;
FixedConnectionPool := Self;
Resume;
end;
end;

As you can see from this code, the constructor stores the cleanup delay and timeout values in member fields of the connection pool object. It then creates the semaphore, setting the number of available locks and the maximum number of locks to the size of the pool. As a result, initially the connection pool is created with all connections available. Also, the critical section, which as described earlier is used to synchronize access to TConnectionModule instance reference counts, is created.

Next, the length of the dynamic array of IConnection references, FPool, is set to the number of available connections. This dynamic array is used to hold internal references to active connections (TConnectionModule instances), thereby preventing them from being released (until the TConnectionPool class is destroyed, or the cleanup thread determines that one or more of the connections have gone unused for an extended period of time).

Finally, an instance of the cleanup thread is created. This class is discussed in detail later in this article.

The most important method of the TConnectionPool class is the GetConnection method. This method returns an IConnection (implemented by TConnectionModule), so long as one is available (as controlled by the semaphore). The implementation of this method is shown here:

function TFixedConnectionPool.GetConnection: IConnection;
var
i: Integer;
DM: TConnectionModule;
WaitResult: Integer;
begin
Result := nil;
WaitResult := WaitForSingleObject(Semaphore, FTimeout);
if WaitResult <> WAIT_OBJECT_0 then
raise EConnPoolException.Create('Connection pool timeout. '+
'Cannot obtain a connection');
CriticalSection.Enter;
try
for i := Low(FPool) to High(FPool) do
begin
//If FPool[i] = nil, the IConnection has
//not yet been created. Create it, initialize
//it, and return it. If FPool[i] <> nil, then
//check to see if its RefCount = 1 (only the pool
//is referencing the object).
if FPool[i] = nil then
begin
DM := TConnectionModule.Create(nil);
DM.CriticalSection := Self.CriticalSection;
DM.Semaphore := Self.Semaphore;
FPool[i] := DM;
FPool[i].Connection.Connected := True;
Result := FPool[i];
Exit;
end;
//if FPool[i].FRefCount = 1 then
//the connection is available. Return it.
if FPool[i].RefCount = 1 then
begin
Result := FPool[i];
Exit;
end;
end; //for
finally
CriticalSection.Leave;
end;
end;

As you can see from this code, when GetConnection is invoked, WaitForSingleObject is called using the connection pool's semaphore. If a connection is available, WaitForSingleObject returns immediately, and the available count on the semaphore is reduced by one. If a connection is not currently available, the thread on which GetConnection is invoked is blocked until either a connection becomes available, or the connection timeout expires. If the connection timeout expires, an exception is raised.

When WaitForSingleObject returns Wait_Object_0, the thread invoking GetConnection is being granted access to the connection pool. This means that either there is one or more connections that have not yet been connected, or a previously connected connection is now available (its reference count is one).

This code then enters the critical section (since it might read the RefCount property of the connection module). It then iterates through the FPool dynamic array. It tests each element of this dynamic array for a value of nil, which signals that this connection has not yet been connected. If the element of FPool is nil, an instance of TConnectionModule is created and assigned to this array element (which increments its reference count to one). It is during this process that the critical section and semaphore reference of the connection pool object is passed to the connection module.  Finally, the IConnection implementing object is returned (which increments its reference count to two).

Each time a given element of the dynamic array is found to be not nil, the loop next tests to see if the reference count of the current element is one. If it is one, that means the connection module was previously created and connected, but is not currently in use. In that case, that connection module is returned (which increments its reference count to two).

Once an available connection has returned, the critical section is exited.

Let's consider the semaphore for a moment. When GetConnection is called, one of the available locks on the semaphore is taken when a connection can be obtained. This lock on the semaphore will be released back to the semaphore when the connection is no longer used by an external object (one outside the connection pool). This is signaled when the reference count decrements to one. This code was shown earlier in the TConnectionModule's _Release method.

TCleanupThread

Just about all relevant code concerning this connection pool has been discussed, with the exception of the TCleanupThread class. This class implements a low priority thread that periodically wakes up and inspects the LastAccess property of all instances of the TConnectionModule class. While this operation is not essential for a connection pool, it permits a connection pool to limit the resources it uses on a database server by releasing connections when they are no longer being used.

The following is the declaration of the TClearnupThread class:

//This thread class is used by the connection pool
//object to cleanup idle connections after a
//configurable period of time.
TCleanupThread = class(TThread)
private
FCleanupDelay: Integer;
protected
//When the thread is created, this critical section
//field will be assigned the connection pool's
//critical section. This critical section is
//used to synchronize access to data module
//reference counts.
CriticalSection: TCriticalSection;
FixedConnectionPool: TFixedConnectionPool;
procedure Execute; override;
constructor Create(CreateSuspended: Boolean;
const CleanupDelayMinutes: Integer);
end;

As you can see, the TCleanupThread class contains TCriticalSection and TFixedConnectionPool member fields. As you saw in the TFixedConnetionPool constructor shown earlier in this article, these members are assigned instances of the fixed connection pool's critical section and a reference to the connection pool itself, respectively.

The following code shows the initialization of the TCleanupThread class in its constructor:

constructor TCleanupThread.Create(CreateSuspended: Boolean;
const CleanupDelayMinutes: Integer);
begin
// always create suspended
inherited Create(True); // always create suspended
FCleanupDelay := CleanupDelayMinutes;
//Resume if not created suspended
if not CreateSuspended then
Resume;
end;

As you can see from this overloaded constructor, it begins by creating an instance of the thread as a suspended thread. It then stores the value of the ClearnupDelayMinutes formal parameter in a member field. Then, if the thread was created with a CreateSuspended formal parameter value of False, it resumes the thread.

The code in a thread is executed in that thread's Execute method. The following is the Execute method of this thread:

procedure TCleanupThread.Execute;
var
i: Integer;
begin
while True do
begin
if Terminated then Exit;
//sleep for delay
sleep(FCleanupDelay * 1000 * 60);
if Terminated then Exit;
FixedConnectionPool.CriticalSection.Enter;
try
for i := low(FixedConnectionPool.FPool) to
High(FixedConnectionPool.FPool) do
//if the connection exists, has no external reference,
//and has not been used lately, release it
if (FixedConnectionPool.FPool[i] <> nil) and
(FixedConnectionPool.FPool[i].RefCount = 1) and
(MinutesBetween(FixedConnectionPool.FPool[i].LastAccess, Now) >
FCleanupDelay) then
FixedConnectionPool.FPool[i] := nil;
finally
FixedConnectionPool.CriticalSection.Leave;
end;//try
end;//while
end;

When the thread executes, and after it verifies that it has not been terminate, it calls sleep for some number of minutes defined by the FCleanupDelay value. After the cleanup delay expires, the thread once again checks to see if it has been terminated, after which is enters the critical section and iterates through the array of IConnection references. For each connection that is not nil and whose reference count is not one, it checks to see if the connection has gone unused for greater than the cleanup delay interval. When an unused connection is found, the corresponding value of the IConnection dynamic array is set to nil, which causes the associated TConnectionModule object to be destroyed (through the interface reference count mechanism). This has the effect of closing the connection.

The TFixedConnectionPool Destructor

A number of resources are used by the TFixedConnectionPool class, including the semaphore and critical section. These need to be cleaned up when the fixed connection pool is destroyed. These tasks are performed by the overridden Destroy destructor of the TFixedConnectionPool class, as shown in the following implementation:

destructor TFixedConnectionPool.Destroy;
var
i: Integer;
begin
//Free any remaining connections
CleanupThread.Terminate;
CriticalSection.Enter;
try
for i := Low(FPool) to High(FPool) do
FPool[i] := nil;
SetLength(FPool,0);
finally
CriticalSection.Leave;
end;
CriticalSection.Free;
//Release the semaphore
CloseHandle(Semaphore);
inherited;
end;

Here you see that the destructor begins by terminating the cleanup thread. Next, it enters the critical section and iterates through the connections in the connection pool, setting each to nil (thereby destroying the corresponding TConnectionModule instance). The IConnection dynamic array is then set to a size of zero. Finally, the critical section is exited, the critical section is freed, after which the handle of the semaphore closed.

Using the Fixed Connection Pool

Before you can use the TFixedConnectionPool class, you must invoke its constructor. This is shown in the following pseudo code:

var
ConnPool: TFixedConnectionPool; begin
ConnPool := TFixedConnectionPool.Create(10, 5, 20000);

Here the connection pool is created with a maximum of 10 connections. Unused connections may be cleaned up after five minutes, and an exception will be raised if a connection cannot be obtained within 20 seconds.

The GetConnection method of the connection pool is used to return a connection module, which you must assign to an IConnection interface reference. You then use the returned interfaced object to obtain the actual connection by reading its connection property. This is shown in the following pseudo code:

var
SQLDataSet: TSQLDataSet;
Conn: IConnection;
begin
SQLDataSet := TSQLDataSet.Create(nil);
try
SQLDataSet.CommandText := 'select * from employee';
Conn := ConnPool.GetConnection;
SQLDataSet.SQLConnection := Conn.Connection;
SQLDataSet.Open;
//do something with the result
finally
SQLDataSet.Free;
end;
//The connection is released when Conn goes out of scope
end;

This article was adapted in part from Mastering Multithreading and Other Advanced Delphi Topics by Cary Jensen, one of the Delphi Developer Days 2003 Power Workshops, focused Delphi (TM) training. For information on this an other Delphi Developer Days 2003 Power Workshops, visit http://www.DelphiDeveloperDays.com.

About the Author

Cary Jensen is President of Jensen Data Systems, Inc., a Texas-based training and consulting company that won the 2002 Delphi Informant Magazine Readers Choice award for Best Training. He is the author and presenter for Delphi Developer Days (www.DelphiDeveloperDays.com), an information-packed Delphi (TM) seminar series that tours North America and Europe, and Delphi Developer Days Power Workshops, focused Delphi (TM) training. Cary is also an award-winning, best-selling co-author of eighteen books, including Building Kylix Applications (2001, Osborne/McGraw-Hill), Oracle JDeveloper (1999, Oracle Press), JBuilder Essentials (1998, Osborne/McGraw-Hill), and Delphi In Depth (1996, Osborne/McGraw-Hill). For information about onsite training and consulting you can contact Cary at cjensen@jensendatasystems.com, or visit his Web site at www.JensenDataSystems.com.

Click here for the current schedule of Delphi Developer Days Power Workshops, focused Delphi (TM) training.

New!: Stay informed, stay in touch. Register online to receive the free Developer Days ELetter: information, observations, and events for the Delphi developer by Cary Jensen. Each Developer Days ELetter includes Delphi tips and tricks, .NET information, links to recent articles posted to the Borland Developers Network site, and events in the Delphi community. Click here to sign up now.

 

Using Semaphores in Delphi, Part 2: The Connection Pool的更多相关文章

  1. Cannot get a connection, pool exhausted解决办法

    http://blog.163.com/it_message/blog/static/8892051200908102032653/ 连接池(Tomcat+oracle),运行一段时间后就会出现 Ca ...

  2. tomcat异常: Cannot get a connection, pool exhausted

    1 问题描述Web程序在tomcat刚开始运行时速度很快,但过一段时间后发现速度变得很慢. 检查日志输出,发现异常如下:org.apache.commons.dbcp.SQLNestedExcepti ...

  3. Database Connection Pool Library | Libzdb

    Database Connection Pool Library | Libzdb A small, easy to use Open Source Database Connection Pool ...

  4. java.lang.IllegalStateException: Connection pool shut down

    最近使用HttpClient 4.5 使用 CloseableHttpClient 发起连接后,使用CloseableHttpResponse 接受返回结果,结果就报错了,上网查了下,有位stacko ...

  5. 连接池(Connection Pool)技术

    解释: 连接池(Connection Pool)技术的核心思想是:连接复用,通过建立一个数据库连接池以及一套连接使用.分配.管理策略,使得该连接池中的连接可以得到高效.安全的复用,避免了数据库连接频繁 ...

  6. Firebird Connection pool is full

    今天在做Firebird V3.0.3  x64 版本内存测试,本地PC上,准备开启800个事务(保持不关闭),每个事务做些事,尽量不释放内存. 每次测试当事务数达到时,就提示Connection p ...

  7. jdbc、Connection pool、jndi的理解和关系

    一.概念和理解: ①.jdbc:Java Data Base Connectivity,java数据库连接,最为传统的一种方式,直接连接操作数据库,需要连接时创建连接,使用结束时销毁连接. ②.Con ...

  8. Android开发中使用数据库时出现java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.

    最近在开发一个 App 的时候用到了数据库,可是在使用数据库的时候就出现了一些问题,在我查询表中的一些信息时出现了一下问题: Caused by: java.lang.IllegalStateExce ...

  9. connection pool exhausted

    1.发现问题 生产环境发现有一些redis报错日志 connection pool exhausted.如果redis中没有数据 就直接回源 查DB.暂时不会有什么大问题.中文意思是连接池耗尽. 2. ...

  10. Can not find connection pool config file

    暂时未解决 checkActivation=====================true Can Not Parse ConnectionCfg! 2019/10/12-11:23:38 > ...

随机推荐

  1. selenium爬取PDF预览文件

    python selenium 爬取某网站的pdf预览文件,下载图片转换pdf 参考链接:https://blog.csdn.net/weixin_44740756/article/details/1 ...

  2. 修改mysql多个表的相同字段为同一值内容

    mysql将所有数据库的表的相同字段更新为某一值 1.创建存储过程函数名为:proc_update_client_id CREATE PROCEDURE `proc_update_client_id` ...

  3. Rest-Assured发送POST请求:创建Hello-imook

    package heyuan.RestAssuredDemo;import static org.junit.jupiter.api.Assertions.*;import org.junit.jup ...

  4. IPC,进程间通信

    信号机制 也叫软中断,软件层次上对中断的模拟 kill -9 加进程号可以终止进程 linux下执行kill -l可以看到 这里面居然没有32 33 直接从31到34 所以一共是62个信号 1) SI ...

  5. HTML学习笔记4----更多元素

    随笔记录方便自己和同路人查阅. #------------------------------------------------我是可耻的分割线--------------------------- ...

  6. EPLAN部件库汇总贴转

    经常看到网友求EPLAN部件库,授人以鱼不如授人以渔,大可不必把所有部件装上,比如软件自带的部件库,我都弃之不用,新建一个部件库,把工作中经常要用到的部件慢慢添加进去.慢慢的你会发现:画图效率越来越高 ...

  7. python监控文件变化

    网址: https://blog.csdn.net/qq_40223983/article/details/102889329 起步在python中文件监控主要有两个库,一个是pyinotify,一个 ...

  8. Windows Server 2012 R2安装.NET Framework4.7.1

    1.KB2919442 https://www.microsoft.com/zh-cn/download/confirmation.aspx?id=42153 2.clearcompressionfl ...

  9. crontab执行不生效

    背景:不知道什么原因脚本手动执行:正常:crontab执行不生效: 1.将命令所属路径加入到 /etc/crontab中, 2.在shell脚本中加入 source /etc/profile

  10. linux Qt编译自己的动态库(.so),详细全流程

    本篇记录Qt编译动态库全流程 1. 建立工程 首先,打开Qt,新建C++ Library 工程 点击choose之后,输入项目名称为Example,一直下一步即可 生成的项目里边有三个文件,分别是ex ...