MultiThread와 MultiContext

본 장에서는 MultiThread와 MultiContext를 사용하기 위한 설정에 대해 설명한다.

1. 개요

MultiThread와 MultiContext 기능을 사용하기 위해 Tmax 시스템은 Kernel-level 패키지를 지원한다. Thread를 생성하고 소멸하는 등의 프로그램은 개발자가 로직을 고려하여 작성해야 한다. C로 작성된 MultiThread와 MultiContext 애플리케이션은 지원하지만 COBOL로 작성된 MultiThread와 MultiContext 애플리케이션은 지원하지 않는다.

MultiThread와 MultiContext 클라이언트 라이브러리에서는 3.8.15 버전 이후부터 사용할 수 있고, 서버 라이브러리에서는 Tmax 5 SP2 버전 이후부터 사용할 수 있다.

2. 클라이언트 프로그램

본 절에서는 MultiThread와 MultiContext에 대한 클라이언트 프로그램의 흐름과 프로그램 구현 방법, 예제에 대해서 설명한다.

2.1. 프로그램 흐름

2.1.1. MultiThread

하나의 프로세스 안에 하나 이상의 실행 unit을 가지고 있는 것을 MultiThread라고 한다. 따라서 Tmax MultiThread 애플리케이션에서는 같은 프로세스에서 동시에 여러 개의 서비스를 요청할 수 있다.

다음은 클라이언트 MultiThread 애플리케이션 프로그램의 흐름에 관한 그림이다. 하나의 클라이언트 프로세스는 동시에 2개의 서비스를 호출할 수 있다.

figure multithread
Tmax 클라이언트 MultiThread 애플리케이션

2.1.2. MultiContext

하나의 클라이언트가 Tmax 시스템과 여러 개의 연결을 맺고 통신할 수 있도록 하는 프로그램 기법을 MultiContext라고 한다.

다음은 클라이언트 MultiContext 애플리케이션 프로그램의 흐름에 관한 그림이다. 하나의 클라이언트는 여러 개의 컨텍스트를 가지며 각 컨텍스트는 Tmax 시스템과 각각 하나씩의 연결을 맺고 통신한다. 따라서 MultiContext를 사용하는 경우 Tmax 시스템은 하나의 사용자로 인식한다.

figure multicontext
Tmax 클라이언트 MultiContext 애플리케이션

2.2. 프로그램 구현

MultiThread와 MultiContext를 사용하기 위해서는 다음의 3가지 루틴을 따라 클라이언트 프로그램을 작성해야 한다.

  1. 시작 구문

  2. 구현 구문

  3. 종료 구문

클라이언트 프로그램 구현을 위해 사용되는 각 함수에 대한 자세한 내용은 Tmax Reference Guide를 참고한다.

시작 구문

MultiThread 및 MultiContext는 다음의 함수로 시작할 수 있다.

  • tpstart()

    int tpstart (TPSTART_T *tpinfo )

    Tmax 시스템과 연결하는 함수로 첫 번째 인자인 TPSTART_T의 멤버인 flags를 TPMultiContextS 또는 TPSINGLECONTEXT로 설정하여 MultiContext 또는 SingleContext로 Tmax 시스템과 연결할 수 있다. flags에 아무런 값도 설정하지 않는다면 TPSINGLECONTEXT가 기본적으로 설정되어 SingleContext로 동작하게 된다.

    또한 클라이언트는 한 번 SingleContext로 설정하면 계속해서 SingleContext로만 동작하게 되고, MultiContext로 설정하면 계속해서 MultiContext로 사용하게 된다.

    tpstart()에 대한 자세한 내용은 tpstart를 참고한다.

  • tpsetctxt()

    int tpsetctxt(int ctxtid, long flags)

    현재 컨텍스트를 설정하는 함수로 클라이언트 프로그램과 서버 프로그램에서 작성 방법에 차이가 있다. 함수에 대한 자세한 설명은 tpsetctxt를 참고한다.

구현 구문

MultiThread 및 MultiContext는 ATMI 함수들을 사용하여 구현한다. ATMI 함수들을 사용할 때는 반드시 현재 컨텍스트인지를 알고 사용해야 정상적으로 사용할 수 있 다.

  • 동기형 통신

    Tmax 시스템과 클라이언트가 동기형 통신을 하는 경우 현재 컨텍스트가 TPINVALIDCONTEXT가 아닌 경우 정상적으로 수행된다. TPINVALIDCONTEXT란 현재 컨텍스트가 다른 Thread에 의해서 free된 경우를 말한다.

    예를 들어 하나의 thread1에서 tpstart()를 한 후 context1을 갖고, thread2에서는 tpsetctxt()로 context1을 같이 사용하는 경우, thread1에서 처리를 끝내고 tpend()를 하면 context1도 메모리에서 삭제되므로 thread2는 TPINVALIDCONTEXT를 갖게 된다.

    또한 MultiContext가 아닌 경우 tpstart()를 하지 않고도 tpcall() 등의 API를 호출하면 자동으로 Tmax 시스템과 연결을 하였으나 MultiContext인 경우는 반드시 tpstart()를 한 후에 tpcall() 등의 API를 명시적으로 사용해야 한다.

  • 비동기형 통신

    MultiContext를 사용하는 경우 같은 컨텍스트를 같은 2개의 Thread가 있는 경우 하나의 Thread에서 tpacall() 후 다른 Thread에서 tpgetrply()를 사용하여 결과를 가져올 수 있다. 이렇게 사용할 때는 tpacall()이 반드시 먼저 호출되어야 하므로 2개의 Thread 사이에 우선 순위가 반드시 명확한 경우에 사용해야 올바른 결과를 받아올 수 있으므로 조심해서 사용해야 한다.

    동기형 통신과 마친가지로 TPINVALIDCONTEXT가 아닌 경우에 정상적으로 수행된다.

  • 트랜잭션

    하나의 Thread에서 트랜잭션을 시작하였다면, 그 Thread와 같은 컨텍스트를 사용하는 Thread들은 트랜잭션을 시작한 이후부터 하나의 트랜잭션이 된다. 이렇게 사용하는 경우도 비동기형 통신과 마찬가지로 우선 순위가 명확한 경우에 사용한다. 또한 TPINVALIDCONTEXT가 아닌 경우에 정상적으로 수행된다.

종료 구문

tpend() 함수를 통하여 MultiThread 및 MultiContext를 종료한다.

int tpend()

이 함수를 사용하지 않는 경우 컨텍스트와 해당하는 Thread에 대한 정보가 메모리에서 지워지지 않아서 나중에 문제가 발생할 수 있으므로 반드시 컨텍스트를 사용한 후에는 tpend()를 해 주어야 한다.

2.3. 프로그램 예제

클라이언트 프로그램, 서버 프로그램, Makefile의 예제를 설명한다.

클라이언트 프로그램

다음은 클라이언트 프로그램의 예제이다.

/*******************************************************************/
/*               Multi-thread/Multi-context Sample Program         */
/*                                                                 */
/* TmaxSoft Co. / QA                                               */
/* remarks: Tmax의 TOUPPER 서비스가 구동되어 있어야 함.            */
/*******************************************************************/

#include       <pthread.h>
#include        <stdlib.h>
#include        <stdio.h>
#include        <errno.h>
#include        <string.h>
#include        <unistd.h>
#include        <netdb.h>
#include        <sys/types.h>
#include       <usrinc/atmi.h>

#define MAX_CTID_CNT       400       /* 최대 context 수*/
#define MAX_CALL           1
#define NUM_THREADS        2       /* 한 번에 생성할 Thread 수*/
#define NUM_CONTEXTS       40       /* 한 Thread에서 생성할 context 수*/

void *mythread(void *arg);
int svcCall(char* svc, char* arg);

int newContext();
int altContext(int id);
int delContext();

#define CTID_EMPTY       0
#define       CTID_OCCUPIED       1
#define THRERR   (void *)-1
#define THRSUC   (void *)1

extern int errno;
extern int _init_wthr_flag;
int           thr_id;
TPSTART_T* tpinfo;

int main()
{
        void      *retVal;
        char      argData[100];
        int       tcnt = 0;
        int       scnt = 0;

        pthread_t   p_thread[NUM_THREADS];

        memset(argData, 0x00, sizeof(argData));
        strcpy(argData,"...mtmc test...");

       if (tmaxreadenv("tmax.env","TMAX") == -1)
       {
              printf( "tmax read env failed\n" );
              return FALSE;
       }

        tpinfo = (TPSTART_T *)tpalloc("TPSTART", NULL, 0);
        if (tpinfo == NULL)
        {
               printf("[THR:%d]tpinfo tpalloc fail[%d][%s]\n",pthread_self(),
                      tperrno,tpstrerror(tperrno));
        }

#ifdef _MULTI_THREAD_TEST_

       while(scnt<MAX_CALL)
       {
          for ( tcnt=0 ; tcnt<NUM_THREADS ; tcnt++)
          {
              if (pthread_create(&p_thread[tcnt], NULL, mythread,argData))
              {
                  fprintf(stderr, "mythread start fail...[%d]\n", errno);
                  return FALSE;
               }
          }
          for(tcnt=0 ; tcnt<NUM_THREADS ; tcnt++)
          {
              pthread_join(p_thread[tcnt], &retVal);
          }

           scnt++;
          sleep(1);
       }

#else
      if (pthread_create(&p_thread[tcnt], NULL, mythread, argData))
       {
          fprintf(stderr, "mythread start fail...[%d]\n", errno);
          return FALSE;
       }
          pthread_join(p_thread[tcnt], &retVal);
#endif

        tpfree((char *)tpinfo);
        return TRUE;
}


/**********************************************************/
/* Sub Process : myhread                                  */
/**********************************************************/
void *mythread(void *arg)
{
       int i,j,k;

printf("[THR:%d] thread start\n",pthread_self());

#ifdef _MULTI_CONTXT_TEST_
        tpinfo->flags = TPMultiContextS;
        for(i=0;i<NUM_CONTEXTS/2;i++)
       {
              j=newContext();
              k=newContext();

              svcCall("TOUPPER",arg);
              delContext();

               altContext(j);
               svcCall("TOUPPER",arg);
               delContext();

       }

#else
       tpinfo->flags = TPSINGLECONTEXT;
       newContext();
       svcCall("TOUPPER",arg);
       delContext();

#endif

printf("[THR:%d] thread finish\n",pthread_self());

        return THRSUC;
}


/**********************************************************/
/* Sub Process : delContext                               */
/**********************************************************/
int delContext()
{
      int i;
      int id;

      i = tpgetctxt(&id,TPNOFLAGS);

      if (i < 0)
      {
          printf("\t[delContext]tpgetctxt fail[%d][%s]\n",
                  tperrno,tpstrerror(tperrno));
          return -1;
      }

      tpend();
      printf("\t[THR:%d][CTXT:%d]tpend.\n",pthread_self(),id);
      return 1;
}

/**********************************************************/
/* Sub Process : newContext                               */
/**********************************************************/
int newContext()
{
       int i;
       int id;

       i = tpstart(tpinfo);

        if (i < 0)
        {
            printf("\t[newContext]tpstart fail[%d][%s]\n", tperrno, tpstrerror(tperrno));
            tpfree((char *)tpinfo);
            return -1;
        }

        i = tpgetctxt(&id,TPNOFLAGS);

        if (i < 0)
        {
            printf("\t[newContext]tpgetctxt fail[%d][%s]\n", tperrno, tpstrerror(tperrno));
            return -1;
        }
        return id;
}

/**********************************************************/
/* Sub Process : altContext                               */
/**********************************************************/
int altContext(int id)
{
       int i;
       int ret;

       ret = tpsetctxt(id, TPNOFLAGS);

       if (ret < 0)
       {
           printf("\t[altContext]tpsetctxt fail[%d][%s]\n",  tperrno,
                  tpstrerror(tperrno));
           tpfree((char *)tpinfo);
           return -1;
       }

       return 1;
}

/**********************************************************/
/* Sub Process : svcCall                                  */
/**********************************************************/
int svcCall(char* svc, char* arg)
{
       int ret;
       long rlen;
       char *sbuf, *rbuf;
       int  id;

       ret=tpgetctxt(&id,TPNOFLAGS);

       sbuf = (char *)tpalloc("STRING", NULL, 0);
        if (sbuf == NULL)
        {
               printf("\t[svrCall]tpalloc error[%d][%s]\n",tperrno,
                      tpstrerror(tperrno));
               return -1;
        }

        rbuf = (char *)tpalloc("STRING", NULL, 0);
        if (rbuf == NULL)
        {
               printf("\t[svrCall]tpalloc error[%d][%s]\n",tperrno,
                      tpstrerror(tperrno));
               return -1;
        }

        strcpy(sbuf, (char *)arg);

        ret = tpcall(svc, (char *)sbuf, strlen(arg), (char **)&rbuf, (long *)&rlen,
                     TPNOFLAGS);

        if (ret < 0)
        {
               printf("\t[svcCall]tpcall fail.[%d][%s]\n",tperrno,
                      tpstrerror(tperrno));
        }
        else
        {
              printf("\t[THR:%d][CTXT:%d]tpcall success.\n",pthread_self(),id);
        }

        tpfree((char *)sbuf);
        tpfree((char *)rbuf);
}
/**********************************************************/
/*                             END                        */
/**********************************************************/

서버 프로그램

다음은 서버 프로그램의 예제이다.

#include <stdio.h>
#include <usrinc/atmi.h>

TOUPPER(TPSVCINFO *msg)
{
        int  i;

        printf("\tTOUPPER service is started!\n");
        printf("\tINPUT : data=%s\n", msg->data);

        for (i = 0; i < msg->len; i++)
             msg->data[i] = toupper(msg->data[i]);

        printf("\tOUTPUT: data=%s\n", msg->data);

        tpreturn(TPSUCCESS,0,(char *)msg->data, 0,0);
}

Makefile

다음은 클라이언트 프로그램의 Makefile 예제이다.

TARGET = $(COMP_TARGET)
APOBJS = $(TARGET).o

TMAXLIBD = $(TMAXDIR)/lib64

TMAXLIBS = -lcli
#TMAXLIBS =/home/ancestor/tmax/lib/libclid.a

#multi_thread / multi_context 일 경우
CFLAGS = -q64 -O -I$(TMAXDIR) -D_ MULTI_THREAD_TEST_ -D _MULTI_CONTXT_TEST_

#single_thread / multi_context 일 경우
CFLAGS = -q64 -O -I$(TMAXDIR) -D _MULTI_CONTXT_TEST_

LDFLAGS = -brtl

#
.SUFFIXES : .c

.c.o:
       $(CC) $(CFLAGS) $(LDFLAGS) -c $<

#
# client compile
#
$(TARGET): $(APOBJS)
       $(CC) $(CFLAGS) $(LDFLAGS) -L$(TMAXLIBD) -o $(TARGET) $(APOBJS) $(TMAXLIBS)

#
clean:
       -rm -f *.o core $(TARGET)

3. 서버 프로그램

본 절에서는 MultiThread와 MultiContext에 대한 서버 프로그램의 흐름과 구현, 예제를 설명한다.

3.1. 개요

Tmax MultiThread와 MultiContext 서버 라이브러리는 TCS 방식의 서버 라이브러리에 대해서는 지원하나 UCS, RDP 라이브러리는 지원하지 않는다.

MultiThread와 MultiContext 서버 라이브러리는 MultiThread 지원 및 MultiContext 기능을 제공한다. 따라서 하나의 서버 프로세스에서 여러 Thread가 서비스 요청을 동시에 처리하거나 2개 이상의 Thread가 하나의 컨텍스트를 공유하면서 서비스를 처리할 수 있다.

일반적인 Tmax 서버 라이브러리는 MultiContext 기능을 제공하지 않기 때문에 사용자가 생성한 Thread에서는 Tmax 서버 라이브러리가 제공하는 API들을 사용할 수 없다.

MultiThread와 MultiContext 서버 라이브러리를 사용하려면 다음과 같은 사항을 설정해야 한다.

  • 관련된 API를 사용하여 서버 애플리케이션 코드를 작성해야 한다.

  • 서버 프로그램을 빌드할 때 MultiThread와 MultiContext 서버 라이브러리인 libsvrmt.so 또는 tmaxsvrmt.dll을 링크해야 한다.

  • Tmax 환경설정 파일의 SERVER 절에 MultiThread와 MultiContext 서버 관련 항목에 대한 설정을 해야 한다. 해당 서버가 MultiThread와 MultiContext 서버를 사용한다는 정보를 설정한다.

    • 관련 항목 : SVRTYPE, MINTHR, MAXTHR, STACKSIZE, CPC 등

MultiThread를 이용한 프로그래밍은 여러 측면에서 매우 유용한 점을 가지고 있다. 하지만 동시성이나 성능적인 측면 등에서 주의 깊게 코드를 작성하지 않으면 개발자가 원하지 않는 동작을 유발할 수 있다. 따라서 MultiThread 프로그래밍이 가지는 잠재적인 문제점을 잘 이해해야 한다.

MultiThread와 MultiContext를 서버 라이브러리를 사용할 경우 다음의 장단점을 갖는다.

  • 장점

    • 코드가 간단해지고 직관적인 프로그래밍이 가능하다.

    • 서버 프로세스의 수를 감소시킬 수 있다.

  • 단점

    • 동시성 관리나 코드 작성 등의 난이도가 높다.

    • 오류가 발생할 경우 디버깅이 어렵다.

    • MultiThread 프로그램으로 포팅할 경우 기존에 개발된 코드가 Thread-safe한지 검토해야 한다.

    • RM과 연동할 경우 MultiThread를 지원하는지 검토가 필요하다.

위의 장단점을 고려해서 그 필요성을 충분히 검토해야 한다. 서버 프로그램의 수행 중에 문제가 발생하는 경우 로그 메시지를 확인한다. 로그 메시지에 대한 자세한 설명은 Tmax Message Reference Guide를 참고한다.

3.2. 프로그램 흐름

3.2.1. MultiThread

하나의 프로세스 안에 하나 이상의 Thread가 있어서 동시에 여러 서비스 요청을 처리할 수 있다. MultiThread와 MultiContext 서버 라이브러리에서는 Thread를 서비스 Thread사용자 생성 Thread로 구분한다.

서비스 Thread

서비스 Thread는 MultiThread와 MultiContext 서버 라이브러리 자체적으로 관리하는 Thread이다. 서버 라이브러리는 여러 서비스 요청을 동시에 처리하기 위해서 환경설정에 정의된 설정에 따라 일정 개수의 Thread를 자체적으로 생성하고 이들을 Thread Pool로 관리한다.

Thread Pool은 환경설정의 MINTHR, MAXTHR 항목의 설정을 기준으로 동작한다. 서버 프로세스가 부팅할 때 최소 개수의 Thread를 기본적으로 생성하고, 만약 그 이하로 Thread의 개수가 줄어들면 추가로 Thread를 생성하여 최소 개수를 유지한다. 서비스 요청이 증가하여 Thread Pool의 유휴 Thread를 모두 사용하게 된 경우 최대 개수까지 추가로 Thread를 생성하여 서비스 요청을 처리한다.

figure 7 2
Tmax 서버 MultiThread 애플리케이션

서버 프로세스에게 서비스 요청이 들어오게 될 경우 서비스 요청들을 각각의 Thread에서 독립적으로 수행하고 그 결과를 클라이언트로 반환한다.

서비스 루틴의 기본적인 동작은 일반적인 서버 프로그램과 동일하므로 서비스 루틴을 작성할 때 일반 서버 라이브러리에서처럼 작성하면 된다. 단, 여러 서비스 Thread가 같은 서비스 루틴을 수행할 수 있기 때문에 모든 루틴이 Thread-Safe하게 작성되어야 함을 유의한다.

다음은 기본적인 서버 프로그램 작성 흐름의 예이다. 5번을 제외한 부분은 서버 라이브러리에 의해 자동으로 동작하는 부분이다.

  1. 서버 프로세스가 기동하면서 tpsvrinit() 함수를 호출한다.

  2. Thread Pool을 구성하면서 환경설정에 따라서 서비스 Thread를 생성한다.

  3. 각 Thread마다 생성되면 최초 한 번 tpsvrthrinit() 함수를 호출한다.

  4. 이후 서비스 Thread는 Thread Pool에서 대기하며, 서비스 요청이 들어오면 유휴 Thread가 깨어나서 서비스 루틴을 실행한다.

  5. 서비스 루틴에서는 구현 구문에서 기술된 작업 등을 이용하여 수행한다.

  6. tpreturn(), tpforward() 함수 호출 후 클라이언트로 처리 결과를 응답한 뒤 Thread Pool에서 대기한다.

  7. 서버 프로세스가 종료되면 생성된 모든 서비스 Thread는 tpsvrthrdone() 함수를 수행한 뒤 종료한다.

  8. 서버 프로세스는 tpsvrdone() 함수를 호출한 뒤 종료한다.

사용자 생성 Thread

서비스 루틴이나 tpsvrinit(), tpsvrthrinit()과 같은 초기화 루틴에서 사용자가 임의의 Thread를 생성할 수 있다. 사용자가 생성한 Thread는 자신의 시작 루틴을 가지고 있으므로 서버 라이브러리가 관리하는 Thread Pool과는 어떠한 관계도 가지고 있지 않으며, 따라서 서비스 요청이 들어와도 기본적으로 이들 Thread에서는 처리되지 않는다. 이러한 Thread는 Thread의 생성과 소멸 시기를 개발자가 명확하게 고려해야 한다.

tpsvrinit(), tpsvrthrinit(), 서비스 루틴에서 사용자에 의해 생성되는 Thread는 MultiThread와 MultiContext 서버 라이브러리와 독립적으로 동작한다. 따라서 컨텍스트를 가지고 있지 않다. 이들 Thread는 개발자의 목적에 따라 서비스 루틴을 대신할 수도 있고, 서비스 루틴과 함께 동작하거나 또는 서비스 루틴과는 전혀 별개로 동작할 수 있다. 반드시 개발 시 주의사항을 숙지한다.

다음은 사용자 생성 Thread가 서비스 Thread의 컨텍스트를 공유하는 상황에서의 프로그램 작성 흐름의 예이다. 1번과 8번은 서버 라이브러리에 의해 자동으로 동작하는 부분이다.

  1. 서비스 요청이 들어와서 서비스 Thread가 서비스 루틴을 수행하기 시작한다.

  2. 서비스 루틴에서는 tpgetctxt() 함수를 호출하여 자신의 Context-ID를 알아낸다.

  3. 사용자 생성 Thread는 서비스 요청 이전부터 존재했거나 서비스 루틴 내에서 새롭게 생성된다.

  4. 사용자 생성 Thread는 서비스 Thread로부터 Context-ID를 전달받아 tpsetctxt()를 호출하여 컨텍스트를 공유한다.

  5. 두 Thread는 각자 자신의 정해진 루틴을 수행한다. 두 Thread 모두 tpcall(), tpacall() 등의 ATMI API를 호출할 수 있다.

  6. 서비스 Thread에서 tpreturn() 또는 tpforward()를 호출하기 전에 다음과 같은 처리를 수행해야 한다.

    • 모든 동기/비동기 통신 및 대화형 통신 등을 종료한다.

    • 트랜잭션이 진행 중이라면 Commit 또는 Rollback을 수행한다.

    • 사용자 생성 Thread는 tpsetctxt()를 호출하여 컨텍스트를 더 이상 공유하지 않도록 설정한다.

  7. 사용자 생성 Thread는 자신의 소멸 시기를 검토한다.

  8. 서비스 Thread는 tpreturn()을 수행한 뒤 Thread Pool로 반환되어 다음 서비스 요청을 대기한다.

3.2.2. MultiContext

서버 라이브러리에서의 컨텍스트는 하나의 서비스 요청을 처리할 때 필요한 정보이다. MultiThread 환경에서 각 Thread마다 서비스를 독립적으로 처리하기 위해서는 MultiContext 기법이 필요하다.

Thread Pool에서 관리되는 모든 서비스 Thread들은 기본적으로 자신만의 컨텍스트를 가지고 있다. 또한 MultiContext 기법은 여러 Thread가 하나의 컨텍스트를 공유하여 사용할 수 있다. 서비스 Thread와 달리 사용자 생성 Thread는 자신만의 컨텍스트가 존재하지 않는다. 따라서 사용자 생성 Thread에서 tpcall() 등의 Tmax API를 사용하기 위해서는 tpgetctxt(), tpsetctxt() API 호출을 통해서 서비스 Thread의 컨텍스트를 공유해야만 한다. 만약 사용자 생성 Thread가 다른 Thread의 컨텍스트를 공유하지 않은 상태에서 Tmax API를 호출하면 TPEPROTO 에러 코드와 함께 호출이 실패 처리된다.

MultiThread와 MultiContext 서버 라이브러리에서 컨텍스트는 트랜잭션 정보, 동기/비동기 통신 정보, 대화형 통신 정보를 관리한다. 사용자 생성 Thread가 서비스 Thread의 컨텍스트를 공유하면 이들 정보 또한 함께 공유된다.

figure 7 3
Tmax 서버 MultiContext 애플리케이션

3.3. 프로그램 구현

MultiThread와 MultiContext 서버 프로그램을 작성할 경우 다음의 API를 활용하여 개발할 수 있다. 개발자는 주의사항을 참고하여 서버 프로그램을 작성한다.

관련 API

서버 프로그램을 작성할 경우 관련 API는 다음과 같다.

  • tpsvrthrinit

    int tpsvrthrinit(int argc, char *argv[])

    MultiThread와 MultiContext 서버에서만 제공되는 함수이다. MultiThread와 MultiContext 서버에서 tpsvrinit 함수가 호출된 이후에 Thread Pool에서 관리되는 서비스 Thread에 대해서도 Thread를 생성할 때 각각의 Thread마다 고유한 초기화 작업을 수행할 수 있도록 제공하는 초기화 함수이다. tpsvrthrinit()에 대한 자세한 설명은 tpsvrthrinit을 참고한다.

  • tpsvrthrdone

    int tpsvrthrdone()

    MultiThread와 MultiContext 서버에서만 제공되는 함수이다. MultiThread와 MultiContext 서버는 서버 프로세스가 종료될 경우 tpsvrdone 함수를 수행하기에 앞서 서비스 Thread들을 종료시킨다. tpsvrthrdone()에 대한 자세한 설명은 tpsvrthrdone을 참고한다.

  • tpsetctxt

    int tpsetctxt(int ctxtid, long flags)

    현재 컨텍스트를 설정하는 함수로 클라이언트 프로그램과 서버 프로그램에서 작성 방법에 차이가 있다. tpsetctxt()에 대한 자세한 설명은 tpsetctxt를 참고한다.

  • tpgetctxt

    int tpgetctxt(int *ctxtid, long flags)

    함수를 호출하는 Thread에 현재 설정되어 있는 컨텍스트의 ID를 첫 번째 파라미터로 반환한다. tpgetctxt()에 대한 자세한 설명은 tpgetctxt를 참고한다.

이들 API 외에도 서버 라이브러리가 제공하는 API들을 사용할 수 있다. 프로그램 구현을 위해 사용되는 API에 대한 자세한 내용은 Tmax Reference Guide를 참고한다.

개발 시 주의사항

개발자는 서버 프로그램을 작성할 때 다음과 같은 사항들을 주의 깊게 살펴야한다.

서비스 Thread와 사용자 생성 Thread 사이에서 tpsetctxt(), tpgetctxt() 등의 API를 사용할 경우 아래의 사항을 반드시 지켜야 한다.

  • 사용자 생성 Thread의 생성 및 소멸에 대한 시기가 논리적으로 타당하도록 작성해야 한다. 만일 서비스 루틴에서 사용자 생성 Thread를 생성하고 해당 Thread를 소멸하지 않는다면 서비스 루틴이 호출될 때마다 Thread가 생성되어 프로그램에 좋지 않은 영향을 끼칠 수 있다.

  • 사용자 생성 Thread는 기본적으로 컨텍스트를 가지고 있지 않으므로 Tmax 통신 등을 수행할 때 반드시 tpsetctxt()로 컨텍스트를 공유해야 한다.

  • tpreturn() 또는 tpforward() 함수는 반드시 서비스 Thread에서만 호출해야 한다. 사용자 생성 Thread에서는 tpreturn()을 호출하면 안된다.

  • tpreturn() 또는 tpforward() 함수가 호출되는 시점에 완료되지 않은 동기/비동기 통신 또는 대화형 통신 등이 존재할 경우 서비스를 요청했던 클라이언트로 TPESVCERR 에러 코드를 반환하고 서비스는 실패 처리된다.

  • tpreturn() 또는 tpforward() 함수가 호출되는 시점에 사용자 생성 Thread에서 컨텍스트를 계속 공유하고 있으면 안된다. 개발자는 tpreturn()을 호출하기 전에 반드시 사용자 생성 Thread에서 tpsetctxt() 함수를 통해 TPNULLCONTEXT로 컨텍스트 설정을 해제하거나 다른 컨텍스트로 설정하여 tpreturn()을 호출하는 서비스 Thread와의 컨텍스트 관계를 해제해야 한다. 그렇지 않으면 tpreturn()은 클라이언트로 TPESVCERR 에러 코드를 반환하고 서비스는 실패 처리된다.

  • tpsetctxt() 함수는 서비스 Thread에서는 호출할 수 없다. 만약 호출할 경우 TPEPROTO 에러 코드를 반환하고 실패 처리된다.

  • 트랜잭션은 같은 컨텍스트를 공유하는 어느 Thread에서 시작되었든지 단 하나의 Thread에서만 Commit, Rollback이 가능하다.

다음은 앞에서 언급한 내용 이외에 주의해야 할 사항이다.

  • MultiThread와 MultiContext 서버에서 서비스 타임아웃이 발생할 경우 해당 서버 프로세스는 즉시 종료된다. 따라서 동시에 수행되고 있던 다른 서비스 요청들도 함께 취소된다. 이는 특정 Thread가 서비스 수행 도중 타임아웃으로 중단될 경우 어느 상황에서 중단될지 알 수 없기 때문이다. 예를 들어 동기화 자원을 선점한 상태이거나 동적 메모리 할당을 받은 상태에서 중단될 경우 이러한 상황을 풀기 위해서는 코딩이 복잡해질 수 있고, 애플리케이션이 원하지 않는 비정상 동작을 유발할 가능성이 있다.

  • 클라이언트에서 사용하는 tpstart(), tpend() 함수를 사용할 수 없다.

  • 오직 자신의 서버 프로세스에만 제공하는 서비스를 동기/비동기 통신으로 호출할 수 있다. 단, 이 경우 상황에 따라서 Dead Lock이 발생할 수 있으므로 관련된 서비스는 반드시 서비스 타임아웃을 설정하는 등의 Dead Lock이 풀릴 수 있는 방법을 마련해야 한다.

3.4. 서비스 처리 프로그램 예제

클라이언트 프로그램, 서버 프로그램, Makefile의 예제를 설명한다.

서버 프로그램

다음은 서버 프로그램의 예제이다.

#include <stdio.h>
#include <usrinc/tmaxapi.h>

int tpsvrinit(int argc, char **argv)
{
        printf("tpsvrinit()");
        return 1;
}

int tpsvrthrinit(int argc, char **argv)
{
        printf("tpsvrthrinit()");
        return 1;
}

MTOUPPER(TPSVCINFO *msg)
{
        int i;
        printf("MTOUPPER service is started!");
        for (i = 0; i < msg->len; i++)
        msg->data[i] = toupper(msg->data[i]);
        tpreturn(TPSUCCESS,0,msg->data, 0, 0);
}

MTOLOWER(TPSVCINFO *msg)
{
        int i;
        printf("MTOLOWER service is started!");
        for (i = 0; i < msg->len; i++)
        msg->data[i] = tolower(msg->data[i]);
        tpreturn(TPSUCCESS,0,msg->data, 0, 0);
}

int tpsvrthrdone()
{
        printf("tpsvrthrdone()");
        return 1;
}

int tpsvrdone()
{
        printf("tpsvrdone()");
        return 1;
}

Makefile

다음은 서버 Makefile의 예제이다.

TARGET = $(COMP_TARGET)
APOBJS = $(TARGET).o
NSDLOBJ = $(TMAXDIR)/lib/sdl.o

LIBS = -lsvrmt -lnodb
OBJS = $(APOBJS) $(SVCTOBJ)
SVCTOBJ = $(TARGET)_svctab.o

CFLAGS = -I$(TMAXDIR) -D_MCONTEXT

APPDIR = $(TMAXDIR)/appbin
SVCTDIR = $(TMAXDIR)/svct
LIBDIR = $(TMAXDIR)/lib

#
.SUFFIXES : .c

.c.o:
        $(CC) $(CFLAGS) -c $<

#
# server compile
#

$(TARGET): $(OBJS)
        $(CC) $(CFLAGS) -L$(LIBDIR) -o $(TARGET) $(OBJS) $(LIBS) $(NSDLOBJ)
        mv $(TARGET) $(APPDIR)/.
        cp $(TARGET).c $(APPDIR)/.
        rm -f $(OBJS)

$(APOBJS): $(TARGET).c
        $(CC) $(CFLAGS) -c $(TARGET).c


$(SVCTOBJ):
        cp -f $(SVCTDIR)/$(TARGET)_svctab.c .
        touch ./$(TARGET)_svctab.c
        $(CC) $(CFLAGS) -c ./$(TARGET)_svctab.c

clean:
        -rm -f *.o core $(APPDIR)/$(TARGET)

환경설정

다음은 환경설정 파일의 예제이다.

*DOMAIN
tmax1   SHMKEY = 77214, MINCLH = 1, MAXCLH = 1,
        TPORTNO = 8888, BLOCKTIME = 30, MAXCACALL = 1024

*NODE
tmax    TMAXDIR = "/home/test/tmax",
        APPDIR = "/home/test/tmax/appbin",
        PATHDIR = "/home/test/tmax/path",
        TLOGDIR = "/home/test/tmax/log/tlog",
        ULOGDIR = "/home/test/tmax/log/ulog",
        SLOGDIR = "/home/test/tmax/log/slog",
        MAXCPC = 200

*SVRGROUP
svg1    NODENAME = tmax

*SERVER
svrmt1  SVGNAME = svg1, SVRTYPE = "STD_MT",
        MIN = 1, MAX = 1,
        CPC = 10, MINTHR = 5, MAXTHR = 10

*SERVICE
MTOUPPER SVRNAME = svrmt1, SVCTIME = 20
MTOLOWER SVRNAME = svrmt1, SVCTIME = 20

3.5. 컨텍스트 공유 프로그램 예제

클라이언트에서 'MSERVICE' 서비스를 호출한다. 서비스 루틴에서는 사용자 생성 Thread를 생성하고, 사용자 Thread가 서비스 Thread의 컨텍스트를 공유한 뒤 동시에 tpcall()을 통해 서비스를 요청하는 방식으로 동작한다.

서버 프로그램

다음은 서버 프로그램의 예제이다.

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <usrinc/tmaxapi.h>

void * THREAD(void *arg);

typedef struct {
        int ctxtid;
        TPSVCINFO *svcinfo;
} param_t;

int testcall(char *service, char *msg, long flags)
{
        char *sndbuf, *rcvbuf;
        long sndlen, rcvlen;

        sndlen = strlen(msg);
        if((sndbuf = (char *) tpalloc("STRING", NULL, sndlen)) == NULL) {
            printf("Error allocating send buffer, [tperrno:%d]", tperrno);
            return -1;
        }
        if((rcvbuf = (char *) tpalloc("STRING", NULL, 0)) == NULL) {
             printf("Error allocating recv buffer, [tperrno:%d]", tperrno);
             tpfree(sndbuf);
             return -1;
        }

        strcpy(sndbuf, msg);
        if(tpcall(service, sndbuf, sndlen, (char **)&rcvbuf, &rcvlen, flags) == -1)
           printf("tpcall(%s) failed, [tperrno:%d, tpurcode:%d]", service, tperrno,
                   tpurcode);
        else
           printf("tpcall(%s) success, [rcvbuf:%s]", service, rcvbuf);

        tpfree(sndbuf);
        tpfree(rcvbuf);
        return 0;
}

MTOUPPER(TPSVCINFO *svcinfo)
{
        int i;

        printf("MTOUPPER service is started! [len:%d, data:%s]\n", svcinfo->len,
                svcinfo->data);

        for (i = 0; i < svcinfo->len; i++)
                svcinfo->data[i] = toupper(svcinfo->data[i]);

        sleep(1);
        printf("MTOUPPER service is finished!\n");
        tpreturn(TPSUCCESS, 0, svcinfo->data, 0, 0);
}

MTOLOWER(TPSVCINFO *svcinfo)
{
        int i;.

        printf("MTOLOWER service is started! [len:%d, data:%s]\n", svcinfo->len,
                svcinfo->data);
        for (i = 0; i < svcinfo->len; i++)
                svcinfo->data[i] = tolower(svcinfo->data[i]);

        sleep(1);
        printf("MTOLOWER service is finished!\n");
        tpreturn(TPSUCCESS, 0, (char *)svcinfo->data, 0, 0);
}

MSERVICE(TPSVCINFO *svcinfo)
{
        pthread_t tid;
        param_t param;

        printf("MSERVICE service is started!");

        tpgetctxt(&param.ctxtid, 0);
        param.svcinfo = svcinfo;
        pthread_create(&tid, NULL, THREAD, &param);

        testcall("MTOLOWER", svcinfo->data, 0);

        pthread_join(tid, NULL);
        printf("MSERVICE service is finished!");
        tpreturn(TPSUCCESS, 0, svcinfo->data, 0L, 0);
}

void *THREAD(void *arg)
{
        param_t *param;
        TPSVCINFO *svcinfo;

        param = (param_t *)arg;
        svcinfo = param->svcinfo;

        if (tpsetctxt(param->ctxtid, 0) == -1) {
            printf("tpsetctxt(%d) failed, [tperrno:%d]", param->ctxtid, tperrno);
            return NULL;
        }

        testcall("MTOUPPER", svcinfo->data, 0);

        if (tpsetctxt(TPNULLCONTEXT, 0) == -1) {
            printf("tpsetctxt(TPNULLCONTEXT) failed, [tperrno:%d]", tperrno);
            return NULL;
        }
        return NULL;
}

int tpsvrinit(int argc, char *argv[])
{
        printf("do tpsvrinit()");
        return 1;
}

int tpsvrthrinit(int argc, char *argv[])
{
        printf("do tpsvrthrinit()");
        return 1;
}

int tpsvrthrdone(void)
{
        printf("do tpsvrthrdone()");
        return 1;
}

int tpsvrdone(void)
{
        printf("do tpsvrdone()");
        return 1;
}

Makefile

Makefile을 참고한다.

환경설정

다음은 환경설정 파일의 예제이다.

*DOMAIN
tmax1   SHMKEY = 77214, MINCLH = 1, MAXCLH = 1,
        TPORTNO = 8888, BLOCKTIME = 30, MAXCACALL = 1024

*NODE
tmax    TMAXDIR = "/home/test/tmax",
        APPDIR = "/home/test/tmax/appbin",
        PATHDIR = "/home/test/tmax/path",
        TLOGDIR = "/home/test/tmax/log/tlog",
        ULOGDIR = "/home/test/tmax/log/ulog",
        SLOGDIR = "/home/test/tmax/log/slog",
        MAXCPC = 200

*SVRGROUP
svg1    NODENAME = tmax

*SERVER
svrmt2  SVGNAME = svg1, SVRTYPE = "STD_MT",
        MIN = 1, MAX = 1,
        CPC = 10, MINTHR = 5, MAXTHR = 10

*SERVICE
MSERVICE SVRNAME = svrmt2, SVCTIME = 10
MTOUPPER SVRNAME = svrmt2
MTOLOWER SVRNAME = svrmt2