1. 程式人生 > >VC+ADO+多執行緒高效、安全的讀寫資料庫

VC+ADO+多執行緒高效、安全的讀寫資料庫

一、問題介紹

專案需要實時獲取並處理40路相機的現場影象,並將處理結果寫入到資料庫,採用的方案是使用多執行緒技術,建立40個工作者執行緒,每個執行緒建立一個數據庫連線。本文僅將專案中遇到的問題以及解決方法做些記錄。

二、多執行緒連線資料庫

在單執行緒程式中,只需建立一個數據庫連線。在多執行緒中,因為多執行緒是並行處理的(對於多核CPU來說),若按單執行緒方式只建立一個數據庫連線,多執行緒共用此連線,那麼必然存在排隊等待的問題。比較好的方法是每個執行緒建立一個單獨的連線。

  1. 採用ADO技術在多執行緒中建立多個數據庫連線時,必須在每個執行緒中使用CoInitialize(NULL)初始化COM庫。
  2. 執行緒結束時,必須在每個執行緒中使用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. 將連線超時調大,大到足夠完成所有連線的建立。
  2. 連線排隊建立,只有上個連線建立完成,才開始建立下一個連線,直至所有連線建立完成。

方案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;
}

四、總結

多執行緒併發訪問資料庫,解決方法是在每個執行緒中建立一個數據庫連線,需要注意的兩點如下:

  1. 必須在每個執行緒中都呼叫CoInitialize(NULL)和CoUninitialize()來初始化COM庫和釋放COM庫資源。
  2. 同時建立多個數據庫連線時,需要注意資料庫連線超時問題,解決方法是分批建立資料庫連線。