std::async와 std::future 사용 시 주의점

2024. 2. 27. 23:56C, C++

void foo() {
	// 스레드 풀에서 작업 실행
	auto future = std::async([]()
		{
			// 1000ms (1초) 대기 후 foo 출력.
			std::this_thread::sleep_for(std::chrono::milliseconds(1000));
			std::cout << "foo" << std::endl;
		});
}

void main() {
	foo();
	std::cout << "bar" << std::endl;

	while (true) {
		std::this_thread::yield();
	}
}

 

위와 같이 코드가 있을 때 "bar"가 출력되고 1초 뒤에 "foo"가 출력될 것 같지만

실제로 실행해보면 1초 후에 "foo" 와 "bar" 순서로 동시에 출력된다.

 

이는 std::async 함수를 호출할 때 옵션으로 std::launch::async를 지정했을 경우 (기본값)

반환하는 std::future 객체가 소멸자에서 해당 비동기 동작을 join하게 되는데

 

foo() 함수가 종료될 때 해당 future 객체의 소멸자가 호출되면서

1초를 기다린 후에 함수가 종료된다.

 

아래처럼 foo() 함수의 반환형을 std::future로 변경해 std::async의 반환값을 다시 반환해

main() 함수로 제어를 넘기면 원래 의도했던 것 처럼 "bar" 출력 1초 후에 "foo" 가 출력되는 모습을 볼 수 있다.

std::future<void> foo() {
	// 스레드 풀에서 작업 실행
	return std::async(std::launch::async, []()
		{
			// 1000ms (1초) 대기 후 foo 출력.
			std::this_thread::sleep_for(std::chrono::milliseconds(1000));
			std::cout << "foo" << std::endl;
		});
}

void main() {
	auto future = foo();
	std::cout << "bar" << std::endl;

	while (true) {
		std::this_thread::yield();
	}
}

 

 

내부적으로..

std::future는 참조 카운트를 가지고 있고 카운트가 0이 되면 자체적으로 삭제 처리를 진행

  • foo() 에서 반환형으로 넘기지 않으면 카운트가 foo() 함수 종료 시점에 0이 되면서 join() 호출 -> 블로킹
  • foo() 함수에서 반환형으로 넘기게 되면 이동 생성자에 의해서 main() 함수까지 삭제 처리가 지연된다.