합류 가능 std::thread는 바탕 시스템의 실행 스레드에 대응되듯이 지연되지 않은 작업에 대한 future 객체도 시스템 스레드에 대응된다. 따라서 std::thread 객체와 future 객체 모두 시스템 스레드에 대한 핸들이라고 할 수 있다.

 

그런데 std::thread 객체에 대한 파괴는 이전 항목에서 다뤘듯이 모두 안좋은 상황을 만들어내지만 future 객체의 소멸자는 어떨 때는 암묵적인 join, 어떨 때는 암묵적인 detach를 수행한 것 같은 결과를 내고 프로그램이 종료되는 일은 없다.

 

future 객체는 피호출자가 결과를 호출자에게 전송하는 통신 채널의 한쪽 끝과 같다.

보통 비동기적으로 실행되는 피호출자는 자신의 계산 결과를 std::promise 객체를 통해 통신 채널에 기록한다.

그 뒤 호출자는 future 객체를 이용해서 그 결과를 읽게 된다.

 

그런데 호출자가 future 객체에 대해 get을 호출하기도 전에 피호출자의 실행이 끝날수도 있는데 피호출자의 결과는 어디에 저장되는걸까? std::promise는 피호출자의 지역 범위에 있어서 피호출자가 완료되면 같이 파괴되기 때문에 std::promise도 안된다.

future 객체도 안된다. 왜냐면 해당 future 객체를 이용해서 std::shared_future를 생성할 수 있는데, 원본 future가 파괴된 후에도 std::shared_future가 여러번 복사될 수 있기 때문이다.

 

호출자, 피호출자 양쪽 다 피호출자의 결과를 담기에는 적합하지 않다. 그래서 양쪽이 공유할 수 있는 외부 장소에 결과를 담는다.

 

공유 상태의 존재가 중요한 이유는 future 객체 소멸자의 행동을 연관된 공유 상태가 결정하기 때문이다. 조금 더 구체적으로 다음과 같다.

 

◾ std::async를 통해서 시동된 지연되지 않은 작업에 대한 공유 상태를 참조하는 마지막 future 객체의 소멸자는 작업이 완료될 때까지 차단된다. 이 경우 암묵적인 join을 수행한다.

◾ 다른 모든 future 객체의 소멸자는 그냥 해당 future 객체를 파괴한다. 이 경우 암묵적인 detach를 수행하는 것과 비슷하다.

 

쉽게 풀어서 얘기하면 future 객체의 소멸자는 future 객체를 파괴한다는 것이다.

단, 예외가 한가지 존재한다.

 

◾ future 객체가 std::async 호출에 의해 생성된 공유 상태를 참조한다.

◾ 작업의 시동 방침이 std::launch::async이다.

◾ future 객체가 공유 상태를 참조하는 마지막 future 객체이다. std::future의 경우에는 이 조건이 항상 성립한다.

 

위의 세 조건이 모두 성립할 때에만 future 객체의 소멸자는 비동기적으로 실행되는 작업이 완료될 때까지 소멸자의 실행이 차단된다.

std::async로 생성한 작업을 실행하는 스레드에 대해 암묵적인 join을 호출하는 것에 해당된다.

 

 

future 객체에 대한 API는 해당 객체가 std::async 호출에 의해 생긴 공유 상태를 참조하는지를 판단할 수 있는 수단을 제공하지 않아서 소멸자의 차단 여부를 알아내는 것은 불가능하다.

 

물론 예외가 발생하지 않음을 미리 알 수 있으면 future 객체의 소멸자가 차단되지 않는다는 것을 확신할 수 있지만 std::async뿐만 아니라 여러 원인으로도 공유 객체가 생성될 수 있다. 그 중 하나가 std::packaged_task의 사용이다.

 

std::packaged_task로 생성된 객체는 임의의 스레드에서 실행할 수 있고 소멸자의 특별한 행동을 고려한 코드를 작성할 필요가 없다. 이미 해당 std::thread를 조작하는 코드에서 결정이 내려지기 때문이다.

 

◾ future 객체의 소멸자는 그냥 future 객체의 자료 멤버들을 파괴할 뿐이다.

◾ std::async를 통해 시동된 비지연 과제에 대한 공유 상태를 참조하는 마지막 future 객체의 소멸자는 그 작업이 완료될 때까지 차단된다.

+ Recent posts