C++ - thread

mohadang·2022년 10월 15일
0

C++

목록 보기
32/48
post-thumbnail

"https://en.cppreference.com/w/cpp/thread"

쓰레드 라이브러리는 OS마다 다르게 사용 된다.

  • 윈도우 쓰레드
#include <Windows.h>

DWORD WINAPI PrintMessage()
{

}

int main()
{
  DWORD myThreadID;

  HANDLE myHandle = CreateThread(0,0,PrintMessage,NULL,0,&myThreadID);

  WaitForSingleObject(myHandle, INFINITE);
  CloseHandle(myHandle)

  return 0;
}
  • POSIX 쓰레드(Linux)
#include <pthread.h>

void* printMessage()
{

}

int main()
{
  pthread_t thread = 0;

  int result_code = pthread_create(&thread, NULL, printMessage, NULL);

  result_code = pthread_join(thread, NULL):

  return 0;
}

C++11 이전의 멀티쓰레딩

  • C++11 전까지 표준 멀티쓰레딩 라이브러리가 없었음
  • OS마다 멀티쓰레딩 구현이 달랐음
    • 리눅스/유닉스: POSIX 쓰레드(pthreads)
    • 윈도우 쓰레드
    • 윈도우에서 pthread를 사용할 수 있었음(유일하게 ver.1003.1만)

std::thread

  • 표준 C++ 쓰레드

  • 이동(move) 가능

  • 복사 불가능

  • 다른 쓰레드 추가하고 싶으면 새로 만들어야 함.

  • EX) 쓰레드 개체 만들기

#include <thread>

void PrintMessage(const std::string& message)
{
  std::cout << message << std::endl;
}

int main()
{
  std::thread thread(PrintMessage, "message from a child thread.");

  PrintMessage("Message from a main thread");
}
// 좋은 예가 아님...
  • 생성자1
- thread() noexcept;
template<class Function, class... Args>
explicit thread(Function&& f, Args&&... args);
- std::thread emptyThread;
- std::thread printThread(PrintMessage, 10);
  • 생성자2
thread(thread&& other) noexcept;// 이동 생성자, 이동 가능
thread(const thread&) = delete;// delete 처리된 복사 생성자. 복사 불가능.

std::thread printThread(PrintMessage, 10);

std::thread movedThread(std::move(printThread));// OK, printThread는 더 이상 쓰레드가 아님
std::thread copiedThread = movedThread;// 컴파일 에러

Multi Threading Race condition

#include <thread>

void PrintMessage(const std::string& message)
{
  std::cout << message << std::endl;
}

int main()
{
  std::thread thread(PrintMessage, "message from a child thread.");

  PrintMessage("Message from a main thread");
}

//출력
//Message from a child thread.Message from a main thread.
  • 위 코드의 문제점

    • 메인과 자식 쓰레드의 메시지가 동시에 출력됨
    • 자식 쓰레드가 끝나기 전에 메인 쓰레드가 끝남.
  • std::thread::join()

    • 쓰레드 개체가 끝날 때까지 현재 쓰레드를 멈춰 놓는다
    • 이 함수를 호출한 후 쓰레드 개체를 안전하게 소멸시킬 수 있음
  • Ex) 자식 쓰레드가 끝날 때까지 기다리기

#include <thread>

void PrintMessage(const std::string& message)
{
  std::cout << message << std::endl;
}

int main()
{
  std::thread thread(PrintMessage, "message from a child thread.");

  PrintMessage("Message from a main thread");

  thread.join(); <-- 기다림...
}
  • join 이라고 말하는 이유는 옛날 문서에서 그렇게 표현
  • Fork 역시 마찬가지, 가지를 친다.

std::thread::get_id()

  • 쓰레드 ID를 반환한다.
  • Ex) 자식 쓰레드 ID 구하기
#include <thread>

void PrintMessage(const std::string& message)
{
  std::cout << message << std::endl;
}

int main()
{
  std::thread thread(PrintMessage, "message from a child thread.");

  // "id 타입이 운영체제마다 다르다. int 일 수도 있고 다른 타입일 수 도 있다."
  std::thread::id childThreadID = thread.get_id();
  std::stringstream ss;

  ss << childThreadID;

  std::string childThreadIDStr = ss.str();

  PrintMessage("Message from a main thread");

  thread.join();// 기다림...
}
  • 쓰레드마다 고유한 ID가 존재한다.
  • ID 타입은 운영체제마다 타입이 다르다

std::thread::detach()

  • 쓰레드 개체에서 쓰레드를 떼어 낸다
  • 떼어진 쓰레드는 메인 쓰레드와 무관하게 독립적으로 실행됨.
  • Ex) 쓰레드 떼어 내기
#include <thread>

void PrintMessage(const std::string& message, int count)
{
  for(int i = 0; i < count; ++i)
}

int main()
{
  std::thread thread(PrintMessage, "message from a child thread.", 10);

  PrintMessage("Message from a main thread", 1);

  thread.detach();// 떼어냄

  // 종료 될 때 thread를 소멸 시켜도 안전하게 프로그램 제거 할 수 있음, 왜냐하면 이전에 thread를 때어냈으니까...
  // Main 쓰레드가 종료 되더라도 새로 만든 쓰레드는 계속 달리게끔 한다.
}

// 출력
// 1: 1: Message from a child thread.Waiting the child thread...
  • 떼어진 쓰레드를 join 하려면??
thread.detach(); <-- 떼어냄
thread.join(); <-- !!! 불가능 ...
  • 떼어진 쓰레드 join 방지하기
if(thread.joinable())
{
  thread.join();
}

std::thread::joinable()

  • 쓰레드가 실행 중인 활성 쓰레드인지 아닌지 확인한다.

람다와 쓰레딩

  • 람다와 쓰레딩은 꽤 괜찮은 조합
  • 간단하고 한번밖에 안 쓰는 함수라면 람다로 쓰자...
#include <thread>
int main()
{
  auto printMessage = [](const std::string& message)
  {
    std::cout << message << std::endl;
  }

  std::thread thread( printMessage, "message from a child thread.");

  PrintMessage("Message from a main thread");

  thread.join();

}
  • Ex) 배개변수를 참조로 전달하기
#include <thread>
int main()
{
  std::vector<int> list(100, 1);
  int result = 0;

  std::thread thread(
    [](const std::vector<int>& v, int& result)
    {
      for(auto item : v)
      {
        result += item;
      }
    },
    list, std::ref(result)) <-- 인자
  );

  thread.join();

  std::cout << "Result: " << result << std::endl;
}

std::ref()

  • T의 참조를 내포한 reference_wrapper 개체를 반환한다
  • "아웃 파라미터로 결과를 받아올 때 써야 한다."
  • std::thread thread(Sum, list, std::ref(result));

std::this_thread

  • 네입스페이스 그룹

  • 현재 쓰레드에 적용되는 "도우미 함수"들이 있음

    • get_id() : 내 쓰레드의 ID, 함수를 호출하는 쓰레드의 ID
    • sleep_for() : 지금 실행 중인 Thread에 sleep
    • sleep_until() :
    • yield() :
  • Ex) 쓰레드 실행 잠시 멈추기

void PrintCurrentTime(){/*현재 시간 출력*/}
void PrintMessage(const std::string& message)
{
  std::cout << "Sleep now...";
  PrintCurrentTime();

  std::this_thread::sleep_for(std::chrono::seconds(3));

  std::cout << message << " ";
  PrintCurrentTime();
}

int main()
{
  std::thread thread(PrintMessage, "Message from a child thread.");

  PrintMessage("Message from a main Thread.");

  thread.joing();

  return 0;
}
  • Ex) 다른 쓰레드에게 양보하기
void PrintMessage(const std::string& message, std::chrono::seconds delay)
{
  auto end = std::chrono::high_resolution_clock::now() + delay;

  while(std::chrono::high_resolution_clock::now() < end)
  {
    std::this_thread::yield();
  }

  std::cout << message << std::endl;
}

int main()
{
  std::thread thread(PrintMessage, "Message from a child thread.", std::chrono::seconds(3));

  PrintMessage("Message from a main thread", std""chrono::seconds(2));

  thread.join();
}
  • sleep(0)와 같은 방식으로 쓰는 경우가 있다. 이유는 현재 쓰레드가 아닌 다른 쓰레드에게 실행 제어권을 주기 위해
  • sleep(0)와 비슷한 용도가 yield
  • sleep과 비슷하나, sleep이 쓰레드를 일시 정지 상태로 바꾸는 반면, yield는 계속 실행 대기 상태.
profile
mohadang

0개의 댓글