一、问题介绍
项目需要实时获取并处理40路相机的现场图像,并将处理结果写入到数据库,采用的方案是使用多线程技术,创建40个工作者线程,每个线程建立一个数据库连接。本文仅将项目中遇到的问题以及解决方法做些记录。
二、多线程连接数据库
在单线程程序中,只需建立一个数据库连接。在多线程中,因为多线程是并行处理的(对于多核CPU来说),若按单线程方式只建立一个数据库连接,多线程共用此连接,那么必然存在排队等待的问题。比较好的方法是每个线程建立一个单独的连接。
- 采用ADO技术在多线程中建立多个数据库连接时,必须在每个线程中使用CoInitialize(NULL)初始化COM库。
- 线程结束时,必须在每个线程中使用CoUninitialize()释放COM资源。
三、实现代码
下面代码展示了如何建立多线程并在线程中建立数据库连接的过程。数据库为:SQL Server 2012,编译工具为VS2010,ADO技术。
/////// main.cpp //////////
//////////////////////////
#include "iostream"
#include "atlstr.h"
using namespace std;
#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF")
unsigned int __stdcall threadProc(PVOID);//工作者线程函数
int total = 40; //工作者线程总数
int index = 0;
void main()
{
::CoInitialize(NULL);//初始化COM库
for ( int i = 0; i < total; i++)
{
_beginthreadex(NULL,0,threadProc,NULL,0,NULL);
}
getchar(); // 主线程阻塞在此处
}
//工作者线程函数
unsigned int __stdcall threadProc(PVOID)
{
int threadId = index;
index++;
::CoInitialize(NULL);//初始化COM库
_ConnectionPtr pConn;
pConn.CreateInstance("ADODB.Connection");
pConn->ConnectionTimeout = 30; //设置连接超时时间 30s
try
{
pConn->Open("Driver={SQL Server};Server=LENOVO-PC01;Database=dbTest;UID=sa;PWD=mima","","",adConnectUnspecified);
}
catch(_com_error e)
{
::MessageBox(NULL,e.Description(),_T("警告"),MB_OKCANCEL);
}
for (int j = 0 ; j < 100; j++)
{
CString strSql;
strSql.Format(_T("insert into Table3(id,age) values( %d ,%d)"),ThreadId,j);
_variant_t recordset;
pConn->Execute((_bstr_t)strSql,&recordset,adCmdText);
}
if (pConn->State == adStateOpen )
{
pConn->Close();
}
::CoUninitialize();//反初始化COM库
return 0;
}
三、上述代码存在的问题
当线程数一多,使用上述代码百分百会出现问题,提示如下:
原因在于数据库建立连接是个非常消耗资源与时间的工作,同时创建大量连接,完成这些连接的耗时必将非常长,而数据库默认的连接超时为30s,一旦超过这个时间,程序就会传回超时错误。解决方法如下:
- 将连接超时调大,大到足够完成所有连接的创建。
- 连接排队创建,只有上个连接创建完成,才开始创建下一个连接,直至所有连接创建完成。
方案1:
//设置超时时间足够大,此处为3000s
pConn->ConnectionTimeout = 3000; //设置连接超时时间 3000s
方案2,推荐采用,实现代码如下:
/////// main.cpp //////////
//////////////////////////
#include "iostream"
#include "atlstr.h"
using namespace std;
#import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF")
unsigned int __stdcall threadProc(PVOID);//工作者线程函数
int total = 40;//工作者线程总数
int index = 0;
int *flag = new int[total](); // 连接创建成功标志
void main()
{
::CoInitialize(NULL);//初始化COM库
HANDLE *hThread = new HANDLE[total];
for ( int i = 0; i < total; i++)
{
hThread[i] = (HANDLE)_beginthreadex(NULL,0,threadProc,NULL,CREATE_SUSPENDED,NULL);
}
int f = flag[4];
ResumeThread(hThread[0]);
for (int i = 1; i < total ; i++)
{
while(flag[i-1] != 1)
{
//空转
}
ResumeThread(hThread[i]);
}
getchar();
delete [] hThread;
delete [] flag;
}
unsigned int __stdcall threadProc(PVOID)
{
int Threadid = index;
::CoInitialize(NULL);//初始化COM库
_ConnectionPtr pConn;
pConn.CreateInstance("ADODB.Connection");
pConn->ConnectionTimeout = 30;
try
{
pConn->Open("Driver={SQL Server};Server=LENOVO-PC01;Database=huayuTest;UID=sa;PWD=MIMA","","",adConnectUnspecified);
}
catch(_com_error e)
{
::MessageBox(NULL,e.Description(),_T("!!!!"),MB_OKCANCEL);
}
cout << "Connect:" <<index<<" has been established"<<endl;
flag[index] = 1; //
index++;
for (int j = 0 ; j < 100; j++)
{
CString strSql;
strSql.Format(_T("insert into Table3(id,age) values( %d ,%d)"),Threadid,j);
_variant_t recordset;
pConn->Execute((_bstr_t)strSql,&recordset,adCmdText);
}
if (pConn->State == adStateOpen )
{
pConn->Close();
}
::CoUninitialize();//反初始化COM库
return 0;
}
四、总结
多线程并发访问数据库,解决方法是在每个线程中建立一个数据库连接,需要注意的两点如下:
- 必须在每个线程中都调用CoInitialize(NULL)和CoUninitialize()来初始化COM库和释放COM库资源。
- 同时创建多个数据库连接时,需要注意数据库连接超时问题,解决方法是分批创建数据库连接。
转载请注明作者和出处:http://blog.csdn.net/holamirai,未经允许请勿用于商业用途。