Asynchronous Component

프로그램을 제작하다 보면, 필연적으로 Database와 소켓, 파일 관련된 코드를 쓰게 된다.
아무리 CPU 성능이 좋아도 이와 같은 기능을 사용하게 되면 io blocking이 발생하고, 프로그램 성능에 치명적인 문제를 일으키게 되기 때문에 쓰레드를 사용해야 한다.

쓰레드는 판도라의 상자!
쓰레드를 생성하는건 간단하지만, 멀티쓰레드 디버깅을 해본 사람이라면 쓰레드 하나 만드는데도 고심을 많이 할 것이다.
이유는 데드락과 동기화 문제가 발생하면서, 코드분석 난이도가 성큼 올라가버리기 때문이다.

간단한 멀티쓰레드 프로그래밍을 해보자

  396 

  397 

  398 struct Dosomething

  399 {

  400         operator()

  401         {

  402                 while( 1 )

  403                 {

  404                         GetQuery(query, fn);

  405                         //쿼리를 가져옴

  406                         Execute(string query);

  407                         //쿼리 실핼
  408                         Call();

  409                         //콜백함수 호출
  410                 }

  411         }

  412 };

  413 

  414 void OnSelectTbTest()

  415 {

  416         //쿼리콜백
  417 };

  418 

  419 int main()

  420 {

  421         Dosomething t; //쓰레드 돌릴 구조체

  422         thread(ref(t));        //쓰레딩 시작

  423 

  424         t.ExecuteScala("select * from tb_test", OnSelectTbTest); 
                // 쓰레드에안전한명령실행함수

  425 

  426         //DoSomeThing...


보통은 클래스에 멤버 함수 콜백을 이용하지만 편의상 전역 함수로 작성하였다.

아마 대부분은 이러한 구조로 쓰레딩을 처리할것이다.
그리고 반드시 잊지 말아야 할것은 OnSelectTbTest로 콜백을 받았을때 처리방법이다.

OnSelectTbTest의 호출자는 메인 쓰레드가 아닌 워커 쓰레드다.
동시 접근이 가능한 함수이므로 여기에 쓰이는 코드는 모두 쓰레드에 안전해야 한다.


한 두개 정도야 락으로 처리 한다지만
Database 쿼리 같은경우 수십개로 늘어 날수 있는데 할때마다 동기화 처리를 해줄것인가?
그뿐만 아니라 세밀한 락을 걸려면 노가다성이 짙고, 자칫하면 동기화 에러를 발생할수도 있다.

그래서 고안한게 비동기 컴포넌트다.

코드상으로는 똑같이 여러개의 쓰레드로 구현되지만, Lock 이 필요없다.

   86 Game:Game(void)

   87 {

   88         OdbcClient client = new OdbcClient;

   89         client->OnCompleted += new EventHandler<DataEventArg>( this, &Game::OnCompleted );

   90         client->ExecuteScala("select * from tb_test"); //long-running func..

   91         Add( client );

   92 }

   93 

   94 void Game::OnCompleted( Component& sender, DataEventArg* e )

   95 {

   96         if( e->succeed == true )

   97                 printf("Succeed");

   98         else

   99                 printf("Fail");

  100 }

  101 

  102 int main()

  103 {

  104         Game game;

  105         Application.Run(game);

  106 

  107         //DoLoop

  108 }



코드로 보면 위의 함수 콜백과 별반 다르지 않다.
OdbcClient란 콤포넌트는 자체적으로 쓰레드를 생성한다.
그후 Game 클래스에서 비동기 호출을 하고 콜백함수가 올때까지 기다린다.
하지만 전의 코드와는 다르게 OnCompleted의 호출자는 ExecuteScala를 호출한 쓰레드이다!
따라서 OnCompleted에 대해 락을 걸필요가 없다.

내부적으로 함수 호출 큐를 가지고 있기때문에 직접 호출하는게 아니라 메세지 큐로 거쳐서 호출되기 때문이다.

이렇게 함으로써 동기화에 좀더 자유로울수 있고, 데이터만 동기화 함으로써 락의 지연시간을 최소화 할수 있었다.

[어디선가 이러한 구조를 누군가 생각하지 않았을까 했는데, MS가 1년전부터 쓰고 있었다는 글을 보았다.  나의 기술 동기화는 1년 전이란 얘긴가 ㅠㅠ]

by 지나가는행인 | 2009/07/01 22:59 | 트랙백 | 덧글(0)

◀ 이전 페이지          다음 페이지 ▶