劉永寧 中國石化石油勘探開發(fā)研究院南京石油物探研究所 摘要: 多線程的應用程序可充分利用計算機資源,,能有效提高應用程序的運行效率。本文通過實例敘述了使用ORACLE OCI 編寫多線程的應用程序的方法和多線程應用程序的運行機制,。 關鍵字: OCI 線程 互斥 應用程序 將一個較為復雜的應用軟件按功能劃分為若干執(zhí)行不同操作的模塊,,再利用多線程機制使它們同時運行在多處理機或單處理機系統(tǒng)上,就能提高軟件的運行效率,。本文討論多線程的實用性,,并通過實例(二個線程反復輪流地交叉操作進程中二個緩沖區(qū),,以提高CPU的利用率)來說明多線程的運作機制和怎樣利用Oracle提供的編程接口函數(shù),編寫運行在多線程環(huán)境中的數(shù)據(jù)庫應用軟件,。
線程可以看作是由進程產(chǎn)生的一些可執(zhí)行單位,。它們能夠共享進程中相同的代碼段和數(shù)據(jù)段,但它們有自己的程序計數(shù)器,,寄存器和堆棧,。程序中的全局變量對所有線程來講都是公用的。由于多個線程可能會同時訪問這些公用的數(shù)據(jù)元素,,所以要用互斥機制來管理對這些公用數(shù)據(jù)的訪問,,保證它們的正確性。該機制稱為互斥鎖,,它能保證應用程序中同時訪問共享資源的多個線程不發(fā)生矛盾沖突,。 在多處理機系統(tǒng)中多個線程同時運行在不同的處理機上,顯然可以提高軟件的運行速度,;在單處理機系統(tǒng)中,,可用多個線程分開執(zhí)行慢操作(如人機交互,I/O操作等)和快操作(數(shù)據(jù)計算處理),,也可提高軟件的運行效率,。我們考慮顯示地震勘探三維數(shù)據(jù)體程序。常規(guī)的程序流程是: 從數(shù)據(jù)庫中讀數(shù)據(jù) 解壓 三維圖象處理 顯示 由于地震勘探三維數(shù)據(jù)體的數(shù)據(jù)量較大,,從數(shù)據(jù)庫讀數(shù)據(jù)到顯示出圖象的串行操作要花費一些時間,。當數(shù)據(jù)少時,所花時間相對要少,,人有時還感覺不到,;但當數(shù)據(jù)量很大時,這個過程需要花費很多時間,。分析流程中四個步驟就會發(fā)現(xiàn):讀數(shù)據(jù)和顯示部分主要是和外部設備打交道,,讀數(shù)據(jù)還需使用網(wǎng)絡;而解壓和三維圖象處理部分基本上是在主機內(nèi)部作運算,。我們知道對外設上數(shù)據(jù)的存取和計算機運算操作在速度上有很大差異,,當外設與主存儲器之間傳輸數(shù)據(jù)時,CPU有時可能會出現(xiàn)空閑,。也就是說,,在讀數(shù)據(jù)和顯示數(shù)據(jù)的過程中,可能會浪費一些CPU資源,。若采用多線程方式編寫這個程序,,就會提高程序的運行效率。將整個過程分二個線程:一個讀數(shù)據(jù)線程專門負責從數(shù)據(jù)庫中取數(shù)據(jù);另一個顯示線程負責數(shù)據(jù)的解壓,,三維圖象處理和顯示輸出,。在程序中安排二個緩沖區(qū),用于存放數(shù)據(jù),。讀數(shù)據(jù)線程輪流地向這二個緩沖區(qū)中寫數(shù)據(jù),,同樣,顯示線程也跟著輪流地使用緩沖區(qū)中的數(shù)據(jù),。即,當讀數(shù)據(jù)線程向緩沖區(qū)1中寫數(shù)據(jù)時,,顯示線程可處理緩沖區(qū)2中的數(shù)據(jù),;而當讀數(shù)據(jù)線程向緩沖區(qū)2中寫數(shù)據(jù)時,顯示線程可處理緩沖區(qū)1中的數(shù)據(jù),。這樣,,就將一個進程的串行操作改為二個線程的并行操作,可以充分地利用CPU資源,。由于這二個線程可能會同時訪問相同的緩沖區(qū),,這是不允許的,可用互斥鎖機制來協(xié)調(diào)這二個線程之間的關系,。
OCI(Oracle Call Interface)是ORACLE提供的面向程序員的C語言編程接口,是開發(fā)ORACLE數(shù)據(jù)庫應用軟件的較好的工具,。用它開發(fā)出的應用程序運行效率比用Pro*C/C++的要高,。程序員利用其中提供的函數(shù),能訪問Oracle數(shù)據(jù)庫服務器,。OCI應用程序的主要任務之一是處理SQL語句或PL/SQL腳本,,在程序執(zhí)行的過程中將用戶對數(shù)據(jù)庫服務器的請求送到ORACLE服務器,并接收來自服務器的響應,。 Oracle數(shù)據(jù)庫服務器和OCI函數(shù)庫的線程安全特征允許程序開發(fā)人員在多線程環(huán)境中使用OCI,。有了線程安全特征,OCI函數(shù)代碼才具有可重入性,,因此從應用程序的多個線程中同時發(fā)出OCI調(diào)用才不會彼此產(chǎn)生不良影響,。要實現(xiàn)多線程安全化,應用程序必須在調(diào)用OCI初始化函數(shù)時定義mode參數(shù)為OCI_THREADED,它告訴OCI接口層,,本應用程序運行在安全的多線程方式中,。 OCI的OCIThread軟件包提供了一些線程化函數(shù),主要有三種類型,。實際使用情況見后面的編程實例,。 ⑴初始化和結(jié)束函數(shù) 在調(diào)用其他函數(shù)之前必須調(diào)用OCIThreadProcessInit()函數(shù),執(zhí)行OCIThread軟件包的初始化工作,然后再調(diào)用OCIThreadInit()函數(shù),,初始化OCIThread上下文,,供其他OCIThread函數(shù)使用。調(diào)用OCIThreadTerm()函數(shù),,結(jié)束OCIThread接口層的處理,,釋放OCIThread上下文內(nèi)存。 ⑵線程管理函數(shù) 類型為OCIThreadHandle的線程句柄用于表示線程的內(nèi)部數(shù)據(jù)結(jié)構(gòu),。在使用之前,,應該用OCIThreadHndInit()來分配和初始化,用完后應調(diào)用OCIThreadHndDestroy()來釋放內(nèi)存,。用OCIThreadCreate()函數(shù)創(chuàng)建新線程,。用OCIThreadId類型變量來標識一個線程。使用OCIThreadIdInit()來分配和初始化線程ID,而用OCIThreadIdDestroy()來釋放線程ID的結(jié)構(gòu),。用OCIThreadClose()函數(shù)關閉線程,。OCIThreadJoin()函數(shù)允許調(diào)用者線程與其他線程連接,當要想連接的線程正在運行時,,阻塞調(diào)用該函數(shù)的線程,。直到指定的線程運行結(jié)束,這個調(diào)用者線程才被喚醒,,方能繼續(xù)執(zhí)行下去,。 ⑶互斥鎖管理函數(shù) 在應用程序中用類型OCIThreadMutex的變量來表示互斥鎖?;コ怄i在使用之前必須用OCIThreadMutexInit()初始化,,用完后要用OCIThreadMutexDestroy()釋放內(nèi)存結(jié)構(gòu)。一個線程可用OCIThreadMutexAcquire()來掌握一把互斥鎖,,任何時候至多只能有一個線程掌握這把互斥鎖,,掌握這把互斥鎖的線程能夠用OCIThreadMutexRelease()來釋放它。當一個線程掌握這把互斥鎖后,,其它線程若想再掌握這把互斥鎖,,就會被阻塞。直到掌握這把鎖的線程釋放它,,被阻塞的線程之一才能得到它,,獲得互斥鎖的線程才能繼續(xù)執(zhí)行下去。
下面用一個實例來講述多線程方式ORACLE應用程序的編寫和多線程的運行機制,,仍以顯示地震三維數(shù)據(jù)體為例,。將從數(shù)據(jù)庫中讀數(shù)據(jù)當作一個線程,數(shù)據(jù)解壓,,三維圖象處理和顯示當作另一個線程,,在進程中給出二個數(shù)據(jù)緩沖區(qū),使這二個線程輪流交叉使用這二個緩沖區(qū)。并用二把互斥鎖來協(xié)調(diào)這二個線程對緩沖區(qū)的使用,。為能清楚而簡單地說明線程和互斥鎖的使用,,這里僅給出程序的主要代碼段。 #include <oci.h> struct thrs_data { OCIThreadMutex *mutex1; 緩沖區(qū)1互斥鎖 int buffer1[10240]; 緩沖區(qū)1 OCIThreadMutex *mutex2; 緩沖區(qū)2互斥鎖 int buffer2[10240]; 緩沖區(qū)2 int flag; 數(shù)據(jù)處理結(jié)束標志 int start_read; 開始讀數(shù)據(jù)標志 int start_disp; 開始顯示數(shù)據(jù)標志 }; OCIEnv *envhp; OCI環(huán)境句柄 OCIError *errhp; OCI錯誤記錄句柄 void read_fun(dvoid *arg); void disp_fun(dvoid *arg); int main(int argc, char* argv[]) { OCIThreadId *tId1,*tId2; 線程ID句柄 OCIThreadHandle *tHnd1,*tHnd2; 線程句柄 struct thrs_data op_data; 定義數(shù)據(jù) OCI初始化(線程安全性)和分配句柄: OCIEnvCreate((OCIEnv **) &envhp,OCI_THREADED,(dvoid *)0, (dvoid* (*)(dvoid*,size_t))0,(dvoid* (*)(dvoid*,dvoid*,size_t))0, (void (*)(dvoid *, dvoid *)) 0, (size_t) 0,(dvoid **) 0 ); OCIHandleAlloc((dvoid *)envhp, (dvoid **)&errhp, OCI_HTYPE_ERROR,(size_t)0, (dvoid **)0); 線程軟件包和線程初始化: OCIThreadProcessInit(); OCIThreadInit(envhp,errhp); 初始化線程ID和線程句柄: OCIThreadIdInit(envhp,errhp,&tId1); OCIThreadHndInit(envhp,errhp,&tHnd1); OCIThreadIdInit(envhp,errhp,&tId2); OCIThreadHndInit(envhp,errhp,&tHnd2); 分配和初始化互斥鎖: OCIThreadMutexInit(envhp,errhp,&(op_data.mutex1)); OCIThreadMutexInit(envhp,errhp,&(op_data.mutex2)); 創(chuàng)建新的線程,,執(zhí)行線程函數(shù)調(diào)用: op_data.start_read=0; op_data.start_disp=0; OCIThreadCreate(envhp,errhp,read_fun,(dvoid *)&op_data, tId1,tHnd1); OCIThreadCreate(envhp,errhp,disp_fun,(dvoid *)&op_data, tId2,tHnd2); 參數(shù)read_fun和disp_fun是二個線程函數(shù),,op_data是送給線程函數(shù)的變量。 等待線程執(zhí)行完成并關閉線程句柄: OCIThreadJoin(envhp,errhp,tHnd1); OCIThreadClose(envhp,errhp,tHnd1); OCIThreadJoin(envhp,errhp,tHnd2); OCIThreadClose(envhp,errhp,tHnd2); 釋放互斥鎖內(nèi)存: OCIThreadMutexDestroy(envhp,errhp,&(op_data.mutex1)); OCIThreadMutexDestroy(envhp,errhp,&(op_data.mutex2)); 釋放線程ID和線程句柄: OCIThreadIdDestroy(envhp,errhp,&tId1); OCIThreadHndDestroy(envhp,errhp,&tHnd1); OCIThreadIdDestroy(envhp,errhp,&tId2); OCIThreadHndDestroy(envhp,errhp,&tHnd2); 釋放線程上下文: OCIThreadTerm(envhp,errhp); 釋放所有分配的句柄,。 OCIHandleFree((dvoid *)errhp, OCI_HTYPE_ERROR); OCIHandleFree((dvoid *)envhp, OCI_HTYPE_ENV); } 下面是二個線程函數(shù)主要代碼段: void read_fun(dvoid* arg) { 讀數(shù)據(jù)進緩沖區(qū)函數(shù) struct thrs_data *op_data; int n=0; op_data=(struct thrs_data *)arg; for(int k=0;k<5;k++) { 在實際應用中,,此處可為for(;;) ,讓退出循環(huán)的 條件由要讀的實際數(shù)據(jù)確定,這里用5次循環(huán),,是為 了能夠運行給出的框架程序,。 OCIThreadMutexAcquire(envhp,errhp,op_data->mutex1); 獲得互斥鎖 op_data->start_read=1; 告訴顯示線程,讀線程已使用緩沖區(qū) printf("read data into buffer1 ...\n"); 在實際應用中,,此處應調(diào)用“讀數(shù)據(jù)進緩沖區(qū)1”的函數(shù)。 OCIThreadMutexRelease(envhp,errhp,op_data->mutex1); 釋放互斥鎖 在實際應用中,,此處可為:當所有數(shù)據(jù)都讀完時,,使op_data->flag=1;并退出循環(huán)體 OCIThreadMutexAcquire(envhp,errhp,op_data->mutex2); printf("read data into buffer2 ...\n"); 在實際應用中,此處應調(diào)用“讀數(shù)據(jù)進緩沖區(qū)2”的函數(shù),。 if(n==0) while(op_data->start_disp==0); 循環(huán)第一次結(jié)束時要等待顯示線程啟 n=1; 動并使用緩沖區(qū) OCIThreadMutexRelease(envhp,errhp,op_data->mutex2); if(k==2) { 這里的代碼段,,是為了能演示框架程序; op_data->flag=2; 在實際應用中,,此處可為: break; 當所有數(shù)據(jù)都讀完時,,使op_data->flag=2; } 并退出循環(huán)體 } } void disp_fun(dvoid* arg) { 處理和顯示數(shù)據(jù)函數(shù) struct thrs_data *op_data; op_data=(struct thrs_data *)arg; for(;;) { while(op_data->start_read==0); 開始時保證讀數(shù)據(jù)線程先使用緩沖區(qū) OCIThreadMutexAcquire(envhp,errhp,op_data->mutex1); op_data->start_disp=1; 告訴讀數(shù)據(jù)線程,顯示線程已開始使用緩沖區(qū) printf(" display buffer1 ...\n"); 在實際應用中,,此處應調(diào)用“使用緩沖區(qū)1中數(shù)據(jù),,解壓,圖象處理和顯示”的函數(shù),。 OCIThreadMutexRelease(envhp,errhp,op_data->mutex1); if(op_data->flag==1) break; 退出循環(huán)體,,返回 OCIThreadMutexAcquire(envhp,errhp,op_data->mutex2); printf(" display buffer2 ...\n"); 在實際應用中,此處應調(diào)用“使用緩沖區(qū)2中數(shù)據(jù),,解壓,,圖象處理和顯示”的函數(shù)。 OCIThreadMutexRelease(envhp,errhp,op_data->mutex2); if(op_data->flag==2) break; 退出循環(huán)體,,返回 } } thrs_data結(jié)構(gòu)中的幾個變量用于讀數(shù)據(jù)線程和顯示線程的開始控制和結(jié)束控制,。start_read:當二個線程同時啟動或顯示線程先啟動時,保證讀數(shù)據(jù)線程先使用緩沖區(qū),,=1表示讀數(shù)據(jù)線程已使用了緩沖區(qū),;start_disp:在讀數(shù)據(jù)線程對緩沖區(qū)進行第一輪操作時,當它已將2個緩沖區(qū)寫滿,而此時顯示線程還沒有啟動或還沒有使用過緩沖區(qū),,這時應將讀數(shù)據(jù)線程阻塞住,,防止它覆蓋掉緩沖區(qū)中未顯示的數(shù)據(jù),=1表示顯示線程已啟動并已使用了緩沖區(qū),。在后續(xù)交替讀數(shù)據(jù)和顯示數(shù)據(jù)的過程中,,由互斥鎖來協(xié)調(diào)二個線程之間的關系。Flag:用于標識數(shù)據(jù)的結(jié)束,,=1表示在緩沖區(qū)1上結(jié)束,,=2表示在緩沖區(qū)2上結(jié)束。 在PC機LINUX下,,使用ORACLE 8i數(shù)據(jù)庫,,框架程序的編譯連結(jié)命令為: gcc -g -o thread thread.cpp \ -I$ORACLE_HOME/rdbms/demo -I/usr/i386-glibc20-linux/include \ -I$ORACLE_HOME/rdbms/public -I$ORACLE_HOME/network/public \ -L$ORACLE_HOME/lib -lclntsh 參考文獻: oracle 技術資料:《Oracle Call Interface Programmer’s Guide》 |
|