C++11 Thread 생성 방법들

|

함수 포인터를 이용하는 방법

함수 포인터를 이용하는 예제 코드는 다음과 같습니다.

#include <cstdio>
#include <thread>

using namespace std;

void counter(int id, int length) {
  for(int i=1; i<=length; i++) {
    printf("counter[%d] : %d\n", id, i);
  }
}

int main() {
  thread t1(counter, 1, 5);
  thread t2(counter, 2, 7);
  t1.join();
  t2.join();

  return 0;
}

위에서 join()은 각 Thread가 작업 완료될 때까지 Blocking되어 있도록 하는 명령어입니다. Blocking은 일반적으로 자원의 낭비를 가져오기 때문에 실제 프로그램에서는 join()의 사용을 최대한 피하는 것이 좋습니다. 대신 Thread에 메세지(Message)를 처리하는 루틴을 만들고, Thread에 메세지를 보내어서 작업을 수행하는 방식이 좀 더 바람직합니다.


함수 객체를 이용하는 방법

#include <cstdio>
#include <thread>

using namespace std;

class Counter {
 public:
  Counter(int id, int length) {
    mId = id;
    mLength = length;
  }

  void operator()() const {
    for (int i = 1; i <= mLength; i++) {
      printf("counter[%d] : %d\n", mId, i);
    }
  }

 private:
  int mId;
  int mLength;
};

int main() {
  // #1
  thread t1{Counter(1, 5)};

  // #2
  Counter c2(2, 7);
  thread t2(c2);

  // #3
  thread t3(Counter(3, 8));

  t1.join();
  t2.join();
  t3.join();

  return 0;
}

위의 예제에서 3가지 방식이 있었는데 3번째 방식은 특수한 경우(예를 들어 인자로 들어가는 클래스의 생성자에 파라메터가 없는 경우)에 컴파일 에러가 뜰 수 있기 때문에 가급적 첫 번째 방식을 사용하는 편이 더 낫습니다.


람다 표현식을 이용하는 방법

#include <cstdio>
#include <thread>

using namespace std;

int main() {
  thread t1([](int id, int length) {
    for (int i = 1; i <= length; i++) {
      printf("counter[%d] : %d\n", id, i);
    }
  }, 1, 7);

  t1.join();

  return 0;
}


클래스 메소드를 이용하는 방법

#include <cstdio>
#include <thread>

using namespace std;

class Counter {
 public:
  Counter(int id, int length) {
    mId = id;
    mLength = length;
  }

  void loop() const {
    for (int i = 1; i <= mLength; i++) {
      printf("counter[%d] : %d\n", mId, i);
    }
  }

 private:
  int mId;
  int mLength;
};

int main() {
  Counter c1(1, 7);

  thread t1{&Counter::loop, &c1};

  t1.join();

  return 0;
}

이 방법은 특정 인스턴스의 메소드를 별도 Thread로 실행시킬 수 있는 장점이 있습니다.