eygle.com   eygle.com
eygle.com eygle
eygle.com  
 

« IBM AIX Oracle 9i RAC 性能因素 - udp及其他 | 文摘首页 | Oracle Kernel Layer & ORA-600 Code Info »

怎样使用OCI编写多线程的ORACLE应用软件

原文链接: http://space.itpub.net/17267437/viewspace-547181

摘要:

多线程的应用程序可充分利用计算机资源,能有效提高应用程序的运行效率。本文通过实例叙述了使用ORACLE OCI编写多线程的应用程序的方法和多线程应用程序的运行机制。

关键字:

 OCI 线程 互斥 应用程序

 

将一个较为复杂的应用软件按功能划分为若干执行不同操作的模块,再利用多线程机制使它们同时运行在多处理机或单处理机系统上,就能提高软件的运行效率。本文讨论多线程的实用性,并通过实例(二个线程反复轮流地交叉操作进程中二个缓冲区,以提高CPU的利用率)来说明多线程的运作机制和怎样利用Oracle提供的编程接口函数,编写运行在多线程环境中的数据库应用软件。

 

1多线程实用性

线程可以看作是由进程产生的一些可执行单位。它们能够共享进程中相同的代码段和数据段,但它们有自己的程序计数器,寄存器和堆栈。程序中的全局变量对所有线程来讲都是公用的。由于多个线程可能会同时访问这些公用的数据元素,所以要用互斥机制来管理对这些公用数据的访问,保证它们的正确性。该机制称为互斥锁,它能保证应用程序中同时访问共享资源的多个线程不发生矛盾冲突。

在多处理机系统中多个线程同时运行在不同的处理机上,显然可以提高软件的运行速度;在单处理机系统中,可用多个线程分开执行慢操作(如人机交互,I/O操作等)和快操作(数据计算处理),也可提高软件的运行效率。我们考虑显示地震勘探三维数据体程序。常规的程序流程是:

 

由 于地震勘探三维数据体的数据量较大,从数据库读数据到显示出图象的串行操作要花费一些时间。当数据少时,所花时间相对要少,人有时还感觉不到;但当数据量 很大时,这个过程需要花费很多时间。分析流程中四个步骤就会发现:读数据和显示部分主要是和外部设备打交道,读数据还需使用网络;而解压和三维图象处理部 分基本上是在主机内部作运算。我们知道对外设上数据的存取和计算机运算操作在速度上有很大差异,当外设与主存储器之间传输数据时,CPU有时可能会出现空闲。也就是说,在读数据和显示数据的过程中,可能会浪费一些CPU资 源。若采用多线程方式编写这个程序,就会提高程序的运行效率。将整个过程分二个线程:一个读数据线程专门负责从数据库中取数据;另一个显示线程负责数据的 解压,三维图象处理和显示输出。在程序中安排二个缓冲区,用于存放数据。读数据线程轮流地向这二个缓冲区中写数据,同样,显示线程也跟着轮流地使用缓冲区 中的数据。即,当读数据线程向缓冲区1中写数据时,显示线程可处理缓冲区2中的数据;而当读数据线程向缓冲区2中写数据时,显示线程可处理缓冲区1中的数据。这样,就将一个进程的串行操作改为二个线程的并行操作,可以充分地利用CPU资源。由于这二个线程可能会同时访问相同的缓冲区,这是不允许的,可用互斥锁机制来协调这二个线程之间的关系。

 

2OCIOCI线程安全性和线程函数包

OCI(Oracle Call Interface)ORACLE提供的面向程序员C语言编程接口,是开发ORACLE数据库应用软件的较好的工具。用它开发出的应用程序运行效率比用Pro*C/C++的要高。程序员利用其中提供的函数,能访问Oracle数据库服务器。OCI应用程序的主要任务之一是处理SQL语句或PL/SQL脚本,在程序执行的过程中将用户对数据库服务器的请求送到ORACLE服务器,并接收来自服务器的响应。

Oracle数据库服务器和OCI函数库的线程安全特征允许程序开发人员在多线程环境中使用OCI。有了线程安全特征,OCI函数代码才具有可重入性,因此从应用程序的多个线程中同时发出OCI调用才不会彼此产生不良影响。要实现多线程安全化,应用程序必须在调用OCI初始化函数时定义mode参数为OCI_THREADED,它告诉OCI接口层,本应用程序运行在安全的多线程方式中。

 

OCIOCIThread软件包提供了一些线程化函数,主要有三种类型。实际使用情况见后面的编程实例。

⑴初始化和结束函数

在调用其他函数之前必须调用OCIThreadProcessInit()函数,执行OCIThread软件包的初始化工作,然后再调用OCIThreadInit()函数,初始化OCIThread上下文,供其他OCIThread函数使用。调用OCIThreadTerm()函数,结束OCIThread接口层的处理,释放OCIThread上下文内存。

⑵线程管理函数

类型为OCIThreadHandle的线程句柄用于表示线程的内部数据结构。在使用之前,应该用OCIThreadHndInit()来分配和初始化,用完后应调用OCIThreadHndDestroy()来释放内存。用OCIThreadCreate()函数创建新线程。用OCIThreadId类型变量来标识一个线程。使用OCIThreadIdInit()来分配和初始化线程ID,而用OCIThreadIdDestroy()来释放线程ID的结构。用OCIThreadClose()函数关闭线程。OCIThreadJoin()函数允许调用者线程与其他线程连接,当要想连接的线程正在运行时,阻塞调用该函数的线程。直到指定的线程运行结束,这个调用者线程才被唤醒,方能继续执行下去。

⑶互斥锁管理函数

在应用程序中用类型OCIThreadMutex的变量来表示互斥锁。互斥锁在使用之前必须用OCIThreadMutexInit()初始化,用完后要用OCIThreadMutexDestroy()释放内存结构。一个线程可用OCIThreadMutexAcquire()来掌握一把互斥锁,任何时候至多只能有一个线程掌握这把互斥锁,掌握这把互斥锁的线程能够用OCIThreadMutexRelease()来释放它。当一个线程掌握这把互斥锁后,其它线程若想再掌握这把互斥锁,就会被阻塞。直到掌握这把锁的线程释放它,被阻塞的线程之一才能得到它,获得互斥锁的线程才能继续执行下去。

3.多线程应用软件的编制

下面用一个实例来讲述多线程方式ORACLE应 用程序的编写和多线程的运行机制,仍以显示地震三维数据体为例。将从数据库中读数据当作一个线程,数据解压,三维图象处理和显示当作另一个线程,在进程中 给出二个数据缓冲区,使这二个线程轮流交叉使用这二个缓冲区。并用二把互斥锁来协调这二个线程对缓冲区的使用。为能清楚而简单地说明线程和互斥锁的使用, 这里仅给出程序的主要代码段。

 

 

#include <oci.h>

struct thrs_data {

   OCIThreadMutex *mutex1; 缓冲区1互斥锁

   int buffer1[10240];     缓冲区1

   OCIThreadMutex *mutex2; 缓冲区2互斥锁

   int buffer2[10240];     缓冲区2

   int flag;               数据处理结束标志

   int start_read;         开始读数据标志

   int start_disp;         开始显示数据标志

};

 

OCIEnv    *envhp;   OCI环境句柄

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;  定义数据

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));

创建新的线程,执行线程函数调用:

 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);

参数read_fundisp_fun是二个线程函数,op_data是送给线程函数的变量。

等待线程执行完成并关闭线程句柄:

 OCIThreadJoin(envhp,errhp,tHnd1);

 OCIThreadClose(envhp,errhp,tHnd1);

 OCIThreadJoin(envhp,errhp,tHnd2);

 OCIThreadClose(envhp,errhp,tHnd2);

释放互斥锁内存:

 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);

}

下面是二个线程函数主要代码段:

void read_fun(dvoid* arg) {  读数据进缓冲区函数

 struct thrs_data *op_data;

 int n=0;

 op_data=(struct thrs_data *)arg;

 for(int k=0;k<5;k++) {在实际应用中,此处可为for(;;) ,让退出循环的

条件由要读的实际数据确定,这里用5次循环,是为

了能够运行给出的框架程序。

  OCIThreadMutexAcquire(envhp,errhp,op_data->mutex1);获得互斥锁

  op_data->start_read=1;告诉显示线程,读线程已使用缓冲区

  printf("read data into buffer1 ...\n");

在实际应用中,此处应调用"读数据进缓冲区1"的函数。

  OCIThreadMutexRelease(envhp,errhp,op_data->mutex1);释放互斥锁

在实际应用中,此处可为:当所有数据都读完时,使op_data->flag=1;并退出循环体

  OCIThreadMutexAcquire(envhp,errhp,op_data->mutex2);

  printf("read data into buffer2 ...\n");

在实际应用中,此处应调用"读数据进缓冲区2"的函数。

  if(n==0) while(op_data->start_disp==0);循环第一次结束时要等待显示线程启

  n=1;                                   动并使用缓冲区

  OCIThreadMutexRelease(envhp,errhp,op_data->mutex2);

  if(k==2) {         这里的代码段,是为了能演示框架程序;

    op_data->flag=2; 在实际应用中,此处可为:

    break;           当所有数据都读完时,使op_data->flag=2;

  }                  并退出循环体

 }

}

 

void disp_fun(dvoid* arg) { 处理和显示数据函数

 struct thrs_data *op_data;

 op_data=(struct thrs_data *)arg;

 

 for(;;) {

  while(op_data->start_read==0);开始时保证读数据线程先使用缓冲区

  OCIThreadMutexAcquire(envhp,errhp,op_data->mutex1);

  op_data->start_disp=1; 告诉读数据线程,显示线程已开始使用缓冲区

  printf(" display buffer1 ...\n");

在实际应用中,此处应调用"使用缓冲区1中数据,解压,图象处理和显示"的函数。

  OCIThreadMutexRelease(envhp,errhp,op_data->mutex1);

  if(op_data->flag==1) break; 退出循环体,返回

  OCIThreadMutexAcquire(envhp,errhp,op_data->mutex2);

  printf(" display buffer2 ...\n");

在实际应用中,此处应调用"使用缓冲区2中数据,解压,图象处理和显示"的函数。

  OCIThreadMutexRelease(envhp,errhp,op_data->mutex2);

  if(op_data->flag==2) break;退出循环体,返回

 }

}

  thrs_data结构中的几个变量用于读数据线程和显示线程的开始控制和结束控制。start_read:当二个线程同时启动或显示线程先启动时,保证读数据线程先使用缓冲区,=1表示读数据线程已使用了缓冲区;start_disp:在读数据线程对缓冲区进行第一轮操作时,当它已将2个缓冲区写满,而此时显示线程还没有启动或还没有使用过缓冲区,这时应将读数据线程阻塞住,防止它覆盖掉缓冲区中未显示的数据,=1表示显示线程已启动并已使用了缓冲区。在后续交替读数据和显示数据的过程中,由互斥锁来协调二个线程之间的关系。Flag:用于标识数据的结束,=1表示在缓冲区1上结束,=2表示在缓冲区2上结束。

   PCLINUX下,使用ORACLE 8i数据库,框架程序的编译连结命令为:

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


历史上的今天...
    >> 2012-08-05文章:
    >> 2008-08-05文章:
           萝卜治咳嗽 简单有奇效

By eygle on 2009-08-05 10:14 | Comments (0) | Oracle摘 | 2358 |


CopyRight © 2004~2020 云和恩墨,成就未来!, All rights reserved.
数据恢复·紧急救援·性能优化 云和恩墨 24x7 热线电话:400-600-8755 业务咨询:010-59007017-7040 or 7037 业务合作: marketing@enmotech.com