티스토리 뷰

 비동기적으로 함수를 실행하는 방법은 std::thread 객체를 생성하는 방법과 std::async 객체를 생성하는 방법이다. std::thread 객체를 생성하는 방법이 스레드 기반 프로그래밍을 의미하고, std::async 객체를 이용하는 방법이 과제 기반 프로그래밍을 의미한다. 코드를 살펴보면 다음과 같은 차이가 존재한다.

int doAsyncWork();

// 스레드 기반
std::thread t(doAsyncWork);

// 과제 기반
auto fut = std::async(doAsyncWork); // future 객체가 생성됨

 과제 기반 프로그래밍에서 future 객체가 생성된다는 것에 집중해야 한다. doAsyncWork 함수가 종료되면 반환값이 존재하는 것을 살펴볼 수 있는데 스레드를 사용하는 경우에는 그 반환값에 접근이 불가능하다. 그에 비해 future 객체를 사용하면 반환값에 접근할 수 있다. 더 나아가 예외가 발생했는지도 알 수 있다. 보다 근본적인 차이는 스레드를 보다 추상적으로 사용할 수 있다는 의미이다. 이 말을 이해하기 위해서는 스레드에 대해서 알아보아야 한다.

스레드

 스레드는 3가지로 분류할 수 있다. 어떤 스레드들이 존재하는지 알아보자.

하드웨어 스레드

 실제 연산을 수행하는 스레드를 하드웨어 스레드라고 부른다. CPU 코어 하나당 하나 이상의 하드웨어 스레드를 제공한다.

소프트웨어 스레드

 운영체제가 하드웨어 스레드를 효율적으로 사용하기 위해 만든 스레드를 의미한다. 하나의 소프트웨어 스레드가 블로킹되면 다른 스레드를 실행시킨다.

std::thread

 C++ 표준 라이브러리로 어떤 소프트웨어 스레드에도 대응되지 않을 수 있다. 

스레드의 사용의 주의점

 스레드가 아무리 많아져도 운영체제가 알아서 잘 관리해줄것 같지만 사실은 그렇지 않다. 소프트웨어 스레드가 너무 많아지면 std::thread 를 생성할 때 예외가 발생한다. 그 이전에도 과다구독(oversubscription)이 발생하는데 소프트웨어 스레드하드웨어 스레드가 많아지는 경우에 발생한다. 일반적으로는 성능 저하가 눈에 보이지 않지만 스레드가 늘어날수록 context switch 가 발생하는 상황이 많아지고, CPU 캐시는 히트되지 않게되고 다음 스레드가 사용할 캐시 또한 오염되어 버린다. 문제를 해결할 방법은 과다구독이 일어나지 않도록 하는 것인데 이는 쉽지 않다. 이러한 문제를 누군가에게 떠넘긴다면 문제는 간단해진다. 그 역할이 바로 std::async 이다. 

std::async

 이 라이브러리르 std::thread 와는 조금 다르게 동작한다. 스레드를 생성하지 않을수도 있다는 것이 그 차이점이다. 그렇지만 이것이 문제를 해결하는 근본적인 방법은 아니다. 그럼에도 스케쥴러가 현재 상황을 더 많이 파악하고 있을것은 분명하다.

 한가지 주의할 점은 스레드를 생성하지 않을수도 있기 때문에 반응성에 문제가 있을 수 있다. 이 때에는 시동 방침을 넘겨주어 반드시 스레드를 생성하도록 하는 것이 좋다.

스레드를 직접 다루어야 하는 경우

  • 바탕 스레드(소프트웨어 스레드) 적용 라이브러리의 API에 접근해야 하는 경우
  • 응용 프로그램의 스레드 사용량을 최적화해야 하는, 그리고 할 수 있어야 하는 경우
  • C++ 동시성 API가 제공하는 것 이상의 스레드 적용 기술을 구현해야 하는 경우

 

참고 서적

스콧 마이어스, Effective Modern C++
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함