[C++] 쓰레드(Thread)를 사용하는 방법


Study/C , C++ , MFC  2020. 4. 7. 18:01

안녕하세요. 명월입니다.


이 글은 C++에서 쓰레드(Thread)를 사용하는 방법에 대한 글입니다.


먼저 쓰레드를 사용하는 방법을 알기 전에 쓰레드가 무엇인지 설명하겠습니다.

우리가 프로그램을 작성하고 실행하면 main에서에서 시작해서 함수를 호출하거나 여러 처리를 거쳐 종료를 하게 됩니다. 이것을 하나의 프로세스라고 합니다.

#include <stdio.h>
#include <iostream>
using namespace std;
// 반복문 실행하는 함수
void function(int count) 
{
  // 파라미터로 받은 count 수치만큼 반복문을 실행한다.
  for (int i = 0; i < count; i++) 
  {
    // 콘솔 실행
    cout << " i / count  = " << i << " / " << count << endl;
  }
}
// 실행 함수
int main()
{
  // function 함수를 호출, 10까지 반복문 실행
  function(10);
  // function 함수를 호출, 10까지 반복문 실행
  function(10);
  return 0;
}

위의 예제는 funcion을 호출하고 function을 호출했습니다.

그럼 당연히 첫번째 function이 끝나고 두번째 function이 실행됩니다. 이것이 하나의 프로세스 흐름입니다.


그런데, 이 처리를 병렬로 처리하고 싶을 때가 있습니다. 즉, 첫번째 호출이 끝나기를 기다리는 것이 아니고 function 처리를 넘기고 function를 처리를 넘기고 main 처리를 하는 방식입니다.

#include <stdio.h>
#include <iostream>
// thread를 사용하기 위한 라이브러리
#include <thread>
using namespace std;

// 함수
void thread_function(int count)
{
  // 파라미터로 받은 count 수치만큼 반복문을 실행한다.
  for (int i = 0; i < count; i++)
  {
    // 콘솔 출력
    cout << "i - " << i << endl;
    // 쓰레드의 비활성 상태 대기, 10ms간..
    this_thread::sleep_for(chrono::milliseconds(10));
  }
}
// 실행 함수
int main()
{
  // thraed 생성, thread_function함수를 호출하고 파라미터는 10을 넘긴다.
  thread _t1(thread_function, 10);
  // thraed 생성
  thread _t2(thread_function, 10);
  // 콘솔 출력
  cout << "main process " << endl;

  // thread가 종료될 때까지 대기
  _t1.join();
  _t2.join();
  return 0;
}

위 예제를 보면 main에서 thread_function의 함수를 호출했으나 thread_function의 함수가 종료될 때까지 기다리지 않고 바로 콘솔 출력으로 갑니다.

그래서 main process가 가장 위에 출력이 되는 것입니다.


정확히 하나의 처리가 아닌 마치 병렬로 처리되는 흐름을 비동기 처리라고 하고 이를 쓰레드라고 합니다.

프로세스는 하나의 프로그램이 실행되고 종료되는 처리라고 이야기하면 한 개의 프로세스 안에는 여러개의 쓰레드를 설정할 수 있습니다.


여기서 sleep(this_thread::sleep_for)는 쓰레드를 실행하는 데에 설정된 시간에 처리를 멈추게 하는 함수입니다.

위 예제는 main와 _t1, _t2로 세개로 된 main thread와 두 쓰레드가 있습니다. 그런데 여기서 _t1과 _t2가 끝나기 전에 main process가 끝나게 되면 에러가 발생하게 됩니다.

그 방지를 위해, join으로 thread가 끝나기를 기다리는 함수입니다. 사양에 따라 join을 사용하지 않고 완전 병렬 처리로 만들 수도 있습니다.

#include <stdio.h>
#include <iostream>
// thread를 사용하기 위한 라이브러리
#include <thread>
using namespace std;
// 테스트를 위한 클래스
class Node
{
private:
  // 변수 선언
  int data = 0;
public:
  // 가산을 위한 함수
  void addData(int data)
  {
    this->data += data;
  }
  // 값을 리턴
  int getData()
  {
    return this->data;
  }
};
// 쓰레드를 위한 함수
void thread_function(Node* node)
{
  // 0부터 1000까지 가산 ( 1 + 2 + ... + 999 + 1000 = 500500)
  for (int i = 0; i <= 1000; i++)
  {
    // node 클래스를 가산
    node->addData(i);
  }
}
// 실행 함수
int main()
{
  // Node 선언
  Node node;
  // 쓰레드를 선언, node를 파라미터로 넘긴다.
  thread _t1(thread_function, &node);
  // 쓰레드를 선언, node를 파라미터로 넘긴다.
  thread _t2(thread_function, &node);
  // 쓰레드가 종료할 때까지 대기
  _t1.join();
  _t2.join();
  
  // 최종 Node의 값은?
  cout << "Node - " << node.getData() << endl;

  return 0;
}

위예제는 1부터 1000까지 더하는 예입니다.

1부터 1000까지 더하면 500500의 값이 나오는데 이를 두번 처리했으니 1001000의 값이 나와야 정상입니다. 그런데 이상한 값이 나왔습니다.

이유는 첫번째 쓰레드에서 Node.data를 가져와서 더하고 다시 Node.data에 저장하는 로직으로 돌아갑니다.

그런데 Node.data에서 가져와서 더하고 다시 Node.data에 저장하기 전에 두번째 쓰레드에서 Node.data를 가져와서 더하고 저장해서 그렇습니다.

즉, 쓰레드 간에 처리를 기다리거나 동기화하는 작업이 없기 때문에 발생하는 것입니다.

#include <stdio.h>
#include <iostream>
// thread를 사용하기 위한 라이브러리
#include <thread>
// thread 동기화를 위한 mutex
#include <mutex>
using namespace std;
// 테스트를 위한 클래스
class Node
{
private:
  // 변수 선언
  int data = 0;
public:
  // 가산을 위한 함수
  void addData(int data)
  {
    this->data += data;
  }
  // 값을 리턴
  int getData()
  {
    return this->data;
  }
};
// lock을 위한 mutex
mutex _mutex;
// 쓰레드를 위한 함수
void thread_function(Node* node)
{
  // 0부터 1000까지 가산 ( 1 + 2 + ... + 999 + 1000 = 500500)
  for (int i = 0; i <= 1000; i++)
  {
    // _mutex이 다른 쓰레드 에서 lock 걸려있으면 대기
    // 안걸려 있으면 lock를 걸로 다음 스텝으로 이동
    _mutex.lock();
    // node 클래스를 가산
    node->addData(i);
    // _mutex에 lock 해제
    _mutex.unlock();
  }
}
// 실행 함수
int main()
{
  // Node 선언
  Node node;
  // 쓰레드를 선언, node를 파라미터로 넘긴다.
  thread _t1(thread_function, &node);
  // 쓰레드를 선언, node를 파라미터로 넘긴다.
  thread _t2(thread_function, &node);
  // 쓰레드가 종료할 때까지 대기
  _t1.join();
  _t2.join();
  
  // 최종 Node의 값은?
  cout << "Node - " << node.getData() << endl;

  return 0;
}

이번에는 값이 1001000으로 정상적인 값이 나왔습니다.

위는 제가 stack 값으로 선언을 했기 때문에 main이 종료되면 자도으로 메모리가 해제됩니다. 만약 heap영역에서 사용하게 되면 꼭 join함수 뒤에 쓰레드가 종료된 후에 delete를 해야 합니다.

그렇지 않으면 runtime error가 발생하게 됩니다.


여기까지 C++에서 쓰레드(Thread)를 사용하는 방법에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.