함수를 비동기적으로 실행하는 방법은 크게 두가지로 나뉜다.

 

int doAsyncWork();

std::thread t(doAsyncWork); // 스레드(thread) 기반 프로그래밍

auto fut = std::async(doAsyncWork); // 작업(task) 기반 프로그래밍

스레드 객체에 함수를 넘겨서 실행하는 스레드 기반 프로그래밍과 std::async에 함수 객체를 넘기는 방법이다.

대체로 작업 기반 접근방식이 스레드 기반 접근방식보다 우월하다.

 

일단 스레드 기반 프로그래밍은 넘겨준 함수의 반환값을 돌려받을 수 없지만 작업 기반 접근방식은 반환값을 돌려받을 수 있다. 작업 기반 접근방식이 반환값을 돌려받을 수 있는 이유는 std::async가 돌려주는 future객체에 get이라는 함수가 존재하기 때문에 가능하다. 게다가 넘겨준 함수가 예외를 방출한다면 더욱 더 중요하다. get을 통해 예외에도 접근할 수 있기 때문이다.

반면에 스레드 기반 프로그래밍에서는 넘겨준 함수가 예외를 던지면 std::terminate 호출을 통해 프로그램이 죽게된다.

 

또한 스레드 기반 프로그래밍과 작업 기반 프로그래밍의 좀 더 근본적인 차이는 작업 기반 프로그래밍의 더 높은 추상화에 있다.

 

일단 스레드라는 용어의 의미들을 짚고 넘어가보자.

 

◾ 실제 계산을 수행하는 스레드를 뜻하는 하드웨어 스레드

◾ OS가 하드웨어 스레드들에서 실행되는 모든 프로세서와 일정을 관리하는 데 사용하는 소프트웨어 스레드

◾ C++ 표준 라이브러리의 std::thread

 

소프트웨어 스레드는 한정적인 자원이기 때문에 시스템이 제공할 수 있는 스레드보다 더 많은 스레드를 생성하려고 하면 std::system_error 예외가 발생한다. 설령 함수가 noexcept라고 해도 예외가 발생한다.

 

소프트웨어 스레드가 부족한 상황을 처리하는데에도 std::async가 유용하게 쓰일 수 있다.

스레드 기반 프로그래밍으로 처리하려면 연쇄적인 문제가 계속 발생하기 때문에 쉽지 않다.

하지만 이런 여러가지 문제들을 std::async에 떠넘기면 매우 편리해진다.

 

auto fut = std::async(doAsyncWork);

스레드 관리의 책임을 C++ 표준 라이브러리 구현자로 떠넘겨버린다.

이러면 가용 스레드 부족에 의해 예외를 받을 가능성이 크게 줄어든다.

std::thread나 std::async나 둘 다 스레드를 요청하는데 무슨 차이가 있길래 스레드 부족 예외를 받을 가능성이 크게 줄어들까?

 

그 이유는 std::async가 상황에 따라 스레드를 생성하지 않을 수도 있기 때문이다. 새로운 스레드를 생성하는 대신 get이나 wait을 호출하는 스레드에서 실행하라고 스케줄러에게 요청할 수 있다. 이 때 스케줄러는 상황에 맞춰 선택하게 된다.

 

다만 위와 같은 함수의 결과가 필요한 스레드에서 실행하는 기법은 부하 불균형 문제가 여전히 발생할 수 있다. 아예 사라지는 것은 아니고 대체로 스케줄러가 전반적인 상황을 더 잘 알고있을 가능성이 높을 뿐이다.

또한 실행중인 스레드 중 반응성이 좋아야 하는 스레드가 어떤 것인지도 알 수 없기 때문에 std::launch::async라는 시동 방침(launch policy)을 std::async에 넘겨주는 것이 좋다.

 

 

스레드 기반 프로그래밍에 비해 작업 기반 프로그래밍은 스레드를 일일이 관리해야 하는 수고로움이 없다. 또한 비동기적으로 실행된 함수의 결과를 자연스럽게 조회할 수 있는 수단 역시 존재한다.

다만 스레드를 직접 다루는 것이 적합한 경우도 존재한다.

 

◾ 바탕 스레드 적용 라이브러리의 API에 접근해야 하는 경우

◾ 응용 프로그램의 스레드 사용량을 최적화해야 하는, 그리고 할 수 있어야 하는 경우

◾ C++ 동시성 API가 제공하는 것 이상의 스레드 적용 기술을 구현해야 하는 경우

 

위의 경우들은 흔치 않은 경우라서 대부분은 작업 기반 설계를 사용하는 것이 바람직하다.

 

◾ std::thread API에서는 비동기적으로 실행된 함수의 반환값을 직접 얻을 수 없으며, 만일 그런 함수가 예외를 던지면 프로그램이 종료된다.

◾ 스레드 기반 프로그래밍에서는 스레드 고갈, 과다구독(oversubscription), 부하 균형화, 새 플랫폼으로의 적응을 독자가 직접 처리해야 한다.

◾ std::async와 기본 시동 방침을 이용한 작업 기반 프로그래밍은 그런 대부분의 문제를 알아서 처리해준다.

+ Recent posts