Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>
C++에서의 쓰레드

18. C++에서의 쓰레드

18.1. 쓰레드 튜토리얼

18.2. C++에서 쓰레드 class 디자인하기

이 장은 Ryan Teixeira 에 의해 쓰여졌고, 그 문서는 여기에 있다. .

18.2.1. 소개

멀티 쓰레드 프로그래밍은 점점 인기를 얻고있다. 이 장은 쓰레드를 지원하는 C++ class의 디자인을 보여줄 것이다. mutex나 세마포어같은 쓰레드 프로그래밍의 몇몇 측면은 여기서 논의되지 않는다. 또한 쓰레드의 관리를 위한 시스템 콜들은 일반적인 형태로 나타내었다.

18.2.2. 쓰레드에 대한 간단한 소개

쓰레드를 이해하기 위해서는, 한꺼번에 돌아가는 여러 프로그램을 생각해야한다. 또한, 이 프로그램들이 똑같은 전역변수와 함수들에 접근한다고 생각해보아라. 이 프로그램들은 실에 비유될 수 있고, 그래서 쓰레드라고 불린다. 중요한 차이점이 있다면, 각각의 쓰레드는 다른 쓰레드가 진행하는 것을 기다릴 필요가 없다는 것이다. 모든 쓰레드가 동시에 진행된다. 비유를 하자면, 이들은 육상선수와 같이 아무도 다른 선수를 기다리지 않는다. 각자 자신의 속도로 진행되는 것이다.

왜 쓰레드를 사용하냐고 물어본다면, 쓰레드는 종종 어플리케이션의 성능을 향상시킬 수 있고, 구현하는게 까다롭지 않다. 즉, 조그만 투자로 큰 효과를 볼 수 있는 것이다. 이미지를 서비스하는 이미지 서버 프로그램을 생각해보아라. 이 프로그램은 다른 프로그램으로부터 이미지에 대한 요청을 받는다. 그러면 이 이미지를 데이터베이스에서 찾아 요청을 보낸 프로그램에게 다시 보내준다. 만약 서버가 하나의 쓰레드로 만들어졌다면, 한번에 하나의 프로그램만 요청을 보낼 수 있을 것이다. 만약 프로그램이 이미지를 찾거나 보내주는 중이라면 다른 요청을 처리할 수 없을 것이다. 물론 이러한 시스템을 쓰레드를 이용하지 않고도 만들 수 있지만, 쓰레드를 쓰면, 여러개의 요청을 아주 자연스럽게 처리할 수 있게 된다. 간단한 접근 방법은 하나의 요청당 하나의 쓰레드를 만드는 것이다. 메인 쓰레드는 요청에 따라 쓰레드를 만들어주기만 하면 된다. 그러면 새로 만들어진 쓰레드가 요청하는 프로그램과 대화하면서 서비스를 해주면 된다. 이미지를 찾아서 보낸 후에는 쓰레드가 스스로 종료하면 된다. 이렇게 하면 하나의 요청을 서비스 하는 도중에도 다른 요청을 받을 수 있는 유연한 시스템이 될 것이다.

18.2.3. 기본적인 접근방법

쓰레드를 만들기 위해서는, 쓰레드의 시작점이 될 함수를 명시해야 한다. 운영체제 레벨에서는, 이것이 일반적인 함수이다. 그런데 C++의 class 멤버함수는 시작함수가 될 수 없기 때문에 약간의 트릭을 써야한다. 하지만, 클래스의 static 멤버함수는 가능하다. 이것이 우리가 시작함수로 이용할 것이다. static 멤버함수는 C++ 객체의 this 포인터를 사용할 수 없다. 이들은 오직 static 데이터만 접근할 수 있다. 다행히도 방법이 있다. 쓰레드의 시작점 함수는 인자로 void *를 갖게 되는데, 이를 쓰레드 안에서 어떤 타입으로나 casting 해서 쓸 수 있다. 우리는 이를 static 함수에 this 를 넘겨주기 위해 쓸 것이다. 따라서 static 함수는 이를 casting 하여 static이 아닌 함수를 부르기 위해 쓸 수 있다.

18.2.4. 구현

우리는 약간 제한된 기능을 갖는 쓰레드 class를 만들 것이다. 실제 쓰레드는 이 class가 하는 것보다 훨씬 많은 일들을 할 수 있다.

class Thread
{
   public:
      Thread();
      int Start(void * arg);
   protected:
      int Run(void * arg);
      static void * EntryPoint(void*);
      virtual void Setup();
      virtual void Execute(void*);
      void * Arg() const {return Arg_;}
      void Arg(void* a){Arg_ = a;}
   private:
      THREADID ThreadId_;
      void * Arg_;

};

Thread::Thread() {}

int Thread::Start(void * arg)
{
   Arg(arg); // user 데이터를 저장함.
   int code = thread_create(Thread::EntryPoint, this, & ThreadId_);
   return code;
}

int Thread::Run(void * arg)
{
   Setup();
   Execute( arg );
}

/*static */
void * Thread::EntryPoint(void * pthis)
{
   Thread * pt = (Thread*)pthis;
   pthis->Run( Arg() );
}

virtual void Thread::Setup()
{
        // Setup에 해당하는 일들
}

virtual void Thread::Execute(void* arg)
{
        // 실행할 내용
}

우리가 쓰레드를 C++ 객체로 사용하고자 한다는 것을 이해하는 것이 중요하다. 각각의 객체는 하나의 쓰레드에 대한 인터페이스를 제공한다. 쓰레드와 객체는 다르다. 객체는 쓰레드 없이 존재할 수 있다. 이 구현에서, 쓰레드 자체는 Start 함수가 불릴 때까지 존재하지 않는다.

여기서 user의 인자를 class에 저장한다는데 주의해라. 이는 쓰레드가 시작될 때까지 임시로 이를 저장할 공간이 필요하기 때문이다. 운영체제 쓰레드는 인자를 하나 넘길 수 있게 해주지만, 우리는 this 때문에 이를 직접 넘겨줄 수 없다. 그래서 우리는 인자를 잠시 class에 저장했다가 함수가 시작될 때 다시 꺼내서 넘겨주게 된다.

Thread(); 생성자이다.

int Start(void * arg); 이 함수는 쓰레드를 만들고, 이를 시작하게 해준다. 이 인자는 쓰레드에 데이터를 넘겨주기 위해 사용되고, Start()는 운영체제의 쓰레드 생성 함수를 부름으로써 쓰레드를 만든다.

int Run(void * arg); 이 함수는 건드리면 안되는 함수이다.

static void * EntryPoint(void * pthis); 이 함수는 쓰레드의 시작 점 역할을 한다. 이 함수는 단순히 pthis를 Thread *로 casting해서 Run 함수를 불러준다.

virtual void Setup(); 이 함수는 쓰레드가 만들어진 후, 실행이 시작되기 전에 불려진다. 이 함수를 override 할 때는, 부모 class의 Setup()를 부르는 것을 기억하라.

virtual void Execute(void *); 하고자 하는 일을 위해 이 함수를 override해라.

18.2.5. Thread Class 사용하기

thread class를 사용하기 위해서는, 새로운 class를 만들어야 한다. 그리고 만들고자 하는 기능을 위해 Execute()를 override하면 된다. 또한, Execute가 불리기 전의 초기화를 위해 Setup()을 override할 수도 있다. 만약, Setup()을 override한다면, 부모 class의 Setup()을 부르는 것을 기억하라.

18.2.6. 결론

이 장은 C++로 쓰레드 class의 구현을 살펴보았다. 물론 이것은 단순한 접근방법이고, 더 좋은 디자인을 위한 기초로 쓰일 수 있을 것이다.

만약 코멘트나 제안하고 싶은 것이 있으면, 메일을 써주기 바란다. Ryan Teixeira