우선순위 작업큐 만들기

Active Object Pattern on ACE 에서 이어지는 포스팅 입니다.

 로직스레드에서 시간이 오래 걸리는 작업을 처리해 줄 수 없기 때문에 DB작업 같은 것을 다른 스레드에 요청을 합니다. 작업을 다른 스레드로 넘길때 Queue를 사용하는데, Queue는 FIFO 방식이라 먼저 넣은 작업이 먼저 처리됩니다. 하지만, 어떤 경우에는 작업에 우선순위를 부여하고 싶을때가 있습니다. 로그 INSERT 작업 같은 것은 그리 우선 순위가 높은 작업이 아니지만 계정 정보를 읽어오는 작업은 상당히 높은 우선 순위를 가지죠. 이런 경우에 적용할 수 있는 방법입니다.

 간단히 설명하면 기존 일반작업큐를 우선순위큐로 바꾸면됩니다. 우선순위큐에 대해서는 여기를 참조하시고 ACE에서는 ACE_Message_Queue가 우선순위큐 기능을 제공합니다. 다행히 지난번 작성한 예제에서 작업을 저장해주는 ACE_Activation_Queue가 내부적으로는  ACE_Message_Qeueu를 사용하기 때문에 ACE_Message_Queue가 제공해주는 기능을 그냥 잘 사용하면 됩니다. 작업을 넣을때 enqueue_prio를 사용하고, DB 스레드에서 작업을 꺼낼때는 dequeue_prio 메소드를 사용하면 되죠.

먼저 작업을 넣을때 우선 순위를 부여하는 코드를 추가합니다.
class SelectAccountJob : public ACE_Method_Request
{
public:
    virtual int call (void)
    {
        ACE_DEBUG((LM_INFO, ACE_TEXT ("(%t) Start SelectAccountJob\n")));
        ACE_OS::sleep (1);
        result_.set("Success");
        ACE_DEBUG ((LM_INFO, ACE_TEXT("(%t) End SelectAccountJob\n")));
        return 0;
    }

    void SetPriorty(unsigned long priority)
   {
        ACE_Method_Request::priority(priority);
   }
};


DB스레드에서 작업을 꺼낼때는 dequeue_prio 메소드를 사용합니다.
class DBWorkerThread : public ACE_Task_Base
{
public:
    int svc()
    {
        const int MAX_TIMEOUT = 5;
        while(true)
        {
            ACE_Time_Value tv ((long)MAX_TIMEOUT);
            ACE_Message_Block* mb = NULL;
            this->queue_.queue()->dequeue_prio(mb, &tv);
            if (NULL == mb)
                continue;
            ACE_Method_Request *request =
                reinterpret_cast<ACE_Method_Request *> (mb->base ());

            request->call();
        }
        return 0;
    }
    int put (ACE_Method_Request *req)
    {
        return queue_.enqueue(req);
    }
private:
    ACE_Activation_Queue queue_;
};

 ACE_Activation_Queue가 enqueue_prio는 지원하지만 dequeue_prio는 직접 인터페이스를 지원하지 않습니다. queue메소드로 직접 queue 포인터를 얻어와서 dequeue_prio 하면 됩니다.

작업을 넣을때 우선 순위를 지정해서 넣는 것도 잊지 말아야 하겠죠.
unsigned int priorty = 1
job->SetPriorty(priorty);
workerThread.put(job);

priorty가 낮은 숫자일수록 먼저 처리됩니다.

소스파일을 첨부합니다.
ActiveObjectTest.cpp
1~5번 job을 먼저 넣었지만, 우선순위에 밀려 6~10번 작업이 먼저 수행되는 것을 볼수 있습니다.

by 자바워크 | 2009/12/07 18:00 | Programming | 트랙백 | 덧글(0)

회사 10주년 기념 파티

지난주 토요일에 회사 설립 10주년 기념 파티가 있었습니다.
극장 비슷한 곳을 빌려서 하더군요. 너무 일찍와서 입장불가. 사진출처는 all2one

파티장 내부. 사진 스타일이 참 멋지네요. 역시 출처는 all2one

1999년에 Coburg에서 3명이 시작한 회사가 현재 전 세계 5개의 지사와 직원 수백명이 넘는 회사로 성장한 역사를 보여주는 pt가 있었습니다. 인상적인 것은 2000년에 3명의 프로그래머를 처음으로 채용했는데 그 분들이 지금도 CryENGINE의 중요 파트를 맡고 있더군요. 복도에서 흔히 보았던 사람들이었는데(한명은 시장에서 장사하는 아주머니 처럼 항상 허리에 쌕을 메고 다녀서 더 눈에 뜨이는..ㅎㅎ) 이 사실을 알고 나니 달리 보이더군요. 그 후로는 흔히 알려진대로 공룡demo(X-Isle)를 만들어서 ECTS에서 발표하고 그것을 발전시켜서 Far Cry를 내놓아서 첫번째 성공을 거두게 됩니다.

 pt 끝날때 즈음해서 Cevat이 부모님들에게 특별히 감사인사를 했는데, 꽤 감동적이었습니다. "They made us possible." 로 부모님을 소개하면서 독일어도 영어도 모르는 상태로 독일로 건너와서(터키계) 모든 장애에 도전하며 3형제를 위해 헌신했던 이야기를 하며 목이 메이는 것 같았습니다. 나중에 테이블로 돌아가서 아버지 어머니를 차례대로 따뜻하게 포옹을 하는데 눈물이 나려고 하더군요.

 확실히 Cevat은 외모도 호감이 가게 생긴데다 공식행사에서 이야기하는 것이나 회사에서도 사람을 대할때 보면 스마트하다는 것을 알수 있습니다. 이번에 인간적인 면모도 보게되서 더 호감이 가더군요.
 

어떤 회사가 되었든 프로젝트가 되었던 뭔가 도모를 하기 위해서는 기본이 되는 사람의 재능이 필요하다고 봅니다. 아무것도 없는상태에서 그 사람이 재능을 보여주어야 하고 그것을 보고 실력있는 사람들이 모여드는 것이지요. Crytek의 경우는 Cevat의 재능이 그러했던 것 같습니다.

by 자바워크 | 2009/12/01 07:19 | 기타 | 트랙백 | 덧글(3)

Active Object Pattern on ACE

게임서버를 작성하다보면 로직스레드 혹은 패킷처리 스레드에서 처리할수 없는 작업들이 있습니다. DB작업이 좋은 예인데, 블럭이 걸리기 때문에 별도의 스레드에서 처리를 해주어야 합니다. 정리하자면 로직 스레드에서 DB스레드에 이 작업을 처리해 달라고 요청을 해야하는 상황인데, 이럴때 Active Object 패턴을 적용할 수 있습니다.

int ACE_TMAIN (int, ACE_TCHAR *[])
{
    DBWorkerThread workerThread;
    workerThread.activate();
    while(true)
    {
        SelectAccountJob job;
        workerThread.put(&job);
        ACE_DEBUG ((LM_INFO, ACE_TEXT("(%t) Put job \n")));
        ACE_OS::sleep (1);
    }
    return 0;
}

class DBWorkerThread : public ACE_Task_Base
{
public:
    int svc()
    {
        const int MAX_TIMEOUT = 5;
        while(true)
        {
            ACE_Time_Value tv ((long)MAX_TIMEOUT);
            ACE_Method_Request *request = queue_.dequeue(&tv);
            if (0 == request)
                continue;

            request->call();
        }
        return 0;
    }
    int put (ACE_Method_Request *req)
    {
        return queue_.enqueue(req);
    }
private:
    ACE_Activation_Queue queue_;
};

class SelectAccountJob : public ACE_Method_Request
{
public:
    virtual int call (void)
    {
        ACE_DEBUG((LM_INFO, ACE_TEXT ("(%t) Start SelectAccountJob\n")));
        ACE_OS::sleep (1);
        ACE_DEBUG ((LM_INFO, ACE_TEXT("(%t) End SelectAccountJob\n")));
        return 0;
    }
};

실행하면 아래와 같이 차례대로 실행이 되는 것을 볼수 있습니다.
왼쪽에 숫자가 ThreadID입니다. Put Job과 실제 실행이 각자 다른 스레드에서 실행되고 있습니다. ACE_Method_Request를 상속받아서 call 메서드만 구현해주면 DBWorkerThread에서 알아서 실행해줍니다.

하지만 실제로는 결과값도 받아와야 하는 경우가 많죠. 이럴때 ACE_Future를 사용할 수 있습니다. 

int ACE_TMAIN (int, ACE_TCHAR *[])
{
    DBWorkerThread workerThread;
    workerThread.activate();
    while(true)
    {
        SelectAccountJob job;
        ACE_Future<std::string> future = job.future();
        workerThread.put(&job);
        ACE_DEBUG ((LM_INFO, ACE_TEXT("(%t) Put job \n")));
        std::string result;
        future.get(result);
        ACE_DEBUG ((LM_INFO, ACE_TEXT("(%t) GetResult(%s)\n"), result.c_str()));
    }
    return 0;
}

class SelectAccountJob : public ACE_Method_Request
{
public:
    virtual int call (void)
    {
        ACE_DEBUG((LM_INFO, ACE_TEXT ("(%t) Start SelectAccountJob\n")));
        ACE_OS::sleep (1);
        result_.set("Success");
        ACE_DEBUG ((LM_INFO, ACE_TEXT("(%t) End SelectAccountJob\n")));
        return 0;
    }

    ACE_Future<std::string> &future (void)
    {
        return result_;
    }

private:
    ACE_Future<std::string> result_;
};

Job을 넣기전에 ACE_Future를 발급받습니다. ACE_Future는 결과값을 받을수 있는 proxy라고 할수 있습니다. future.get()하면 결과값을 받을 수 있는데, WorkerThread에서 결과값을 넣기 전이라면 받을수 있을때까지 블럭이 걸립니다. 내부적으로는 event wait를 합니다. WorkerThread에서 작업이 끝나면 signal을 주고 get을 했던 스레드는 블럭이 풀리면서 값을 받아오게 되는 구조입니다. 그전에 작업이 완료되어 있으면 블럭없이 바로 받아올수 있습니다.

WorkerThread에서 작업이 끝나면 callback을 불러주는 식으로 구현도 가능합니다.

int ACE_TMAIN (int, ACE_TCHAR *[])
{
    ...
    CompletionCallBack cb;
    while(true)
    {
        SelectAccountJob* job = new SelectAccountJob();
        ACE_Future<std::string> future = job->future();
        job->attach(&cb);
        workerThread.put(job);
        ACE_DEBUG ((LM_INFO, ACE_TEXT("(%t) Put job \n")));
        ACE_OS::sleep (1);

    }
    getchar();
    return 0;
}

class CompletionCallBack: public ACE_Future_Observer<std::string>
{
public:
    virtual void update (const ACE_Future<std::string> & future)
    {
        std::string result;
        future.get(result);
        ACE_DEBUG ((LM_INFO, ACE_TEXT("(%t) GetResult(%s)\n"), result.c_str()));
    }
};

class SelectAccountJob : public ACE_Method_Request
{
    ...
    void attach (CompletionCallBack *cb)
    {
        result_.attach (cb);
    }
private:
    ACE_Future<std::string> result_;
};

WorkerThread에서 작업이 끝나면 CompletionCallBack::update를 호출해 줍니다. 보통 작업 결과를 확인하고 응답패킷을 보내주는 코드를 넣으면 되겠죠.


by 자바워크 | 2009/11/19 02:03 | Programming | 트랙백 | 핑백(1) | 덧글(0)

플랫폼 독립적인 코드 작성

윈도우용으로 작성한 코드를 리눅스용으로 포팅할 일이 생겼는데, 다행히 ACE 기반으로 작성된 코드라 쉽게 포팅 할 수 있었습니다. 포팅중에 정리해둔 것을 옮겨봅니다.

대소문자의 명확한 구분
파일을 지칭할때 윈도우와 달리 리눅스는 대소문자를 명확하게 구분합니다. 윈도우에서 그냥 넘어갔던 코드가 문제가 생길수 있습니다. 대부분 include에서 문제가 발생합니다.
  • "Stdafx.h" -> "stdafx.h"
  • ace/os_ns_string.h -> ace/OS_NS_string.h
ACE 타입의 사용
gcc에는 tchar.h 가 없기때문에 TCHAR를 모두 ACE_TCHAR로 변경했습니다.
  • TCHAR ->ACE_TCHAR
  • _T("text") - > ACE_TEXT("text")
또하나 유의할점은 TCHAR는 _UNICODE define 유무에 따라 char/wchar이 결정되지만 ACE_TCHAR는 ACE_USES_WCHAR define에 따라 결정됩니다.

_s 함수들
vc에서는 strcpy_s와 같이 _s가 붙은 함수의 사용을 권장하고 컴파일시에 경고메세지도 나옵니다. 하지만 gcc에는 없는 함수이기 때문에 그냥 strcpy함수 를 사용해야 합니다. 뒤에도 나오지만 ACE_OS::strcpy 함수를 쓰는게 가장 좋습니다. 설정에 따라 char/wchar로 switch되기 때문입니다. 그래도 C4996 warning은 피할수 없습니다. _CRT_SECURE_NO_DEPRECATE으로 끄는 수밖에 없습니다.

ACE 함수의 사용
ace/OS_NS_string.h, ace/OS_NS_stdlib.h, ace/OS_NS_stdio.h 에는 문자열과 io를 다루는 함수들이 있습니다. 그 함수들을 사용하면 ACE가 플랫폼과 char/wchar에 맞는 함수들을 호출해줍니다.

  • sprintf -> ACE_OS::sprintf
  • strcpy -> ACE_OS::strcpy
  • atoi -> ACE_OS::atoi
  • atof -> ACE_OS::atof
  • strcmp - >ACE_OS::strcmp
  • strtoul - > ACE_OS::strtoul
  • strlen - >ACE_OS::strlen
  • strcat -> ACE_OS::strcat
  • vsnprintf - >ACE_OS::vsnprintf
  • fopen - >ACE_OS::fopen
  • fread - > ACE_OS::fread

멤버함수 템플릿 특수화의 미묘한 차이
vc에서는 되는데, gcc에서는 inline으로 해결해야 하는 문제가 있더군요. 특정 상황에서 템플릿 멤버함수가 특수화가 되지 않는 현상이었습니다. 아마 부분 특수화 관련 이었던것 같은데, vc의 문제인지 gcc의 문제인지는 알수 없지만 반드시 체크해야 합니다.

GetTickCount() 함수
GetTickCount()는 리눅스에서는 gettimeofday()로 바꾸어주어야 합니다. 그냥 치환만 하면 되는건 아니고 받아오는 단위가 다르기 때문에 변환도 필요합니다. 아래와 같이 만들어서 사용했습니다.
unsigned int  GetTick()
{
#ifdef WIN32
    return GetTickCount();
#else
    timeval tick;
    gettimeofday (&tick, 0);
    return (tick.tv_sec*1000 + tick.tv_usec/1000);
#endif
}

InterlockedCompareExchange 함수
ACE에는 이 함수에 대응하는 함수가 없습니다. 이 외의 interlocked 계열 함수들은 모두 있습니다만 유독 이함수만 없습니다. 그냥 lock걸고 비교하는 방식으로 변경해야합니다.


by 자바워크 | 2009/11/11 21:52 | Programming | 트랙백 | 덧글(2)

독일남부여행 - Garmisch

이번 남부여행의 종착점 Garmisch-Partenkirchen(보통 "가르미슈"라고 하더군요) 입니다. 여기는 독일에서 가장 높은 산인 Zugspitze가 있습니다. 등산열차가 있어서 누구나 쉽게 올라갈 수 있습니다. 올라가서 보니 구름이 밑에 있더군요(+_+). 사진으로 주욱 보시겠습니다.



by 자바워크 | 2009/11/02 02:00 | 기타 | 트랙백 | 덧글(7)

<< 이전 페이지     다음 페이지 >>