강좌

C++ 코루틴(coroutine) 활용 강좌: #2 : co_await

하늘흐늘 2022. 5. 20. 14:34
반응형

이번 강좌는 c++ coroutine의 기본적인 사용법과 co_await를 이용하여 함수 중간에 제어권을 다른 쪽으로 넘기는 것을 살펴보도록 하겠습니다. 구체적으로 실행 중간에 실행권을 멈추고 제어권을 다른 쪽으로 넘기면서 실행되는 함수 A와 B의 예를 살펴보도록 하겠습니다. 예제는 Win32의 Fiber를 사용하는 것과 비슷하게 느껴질 것입니다.

예제 코드는 아래와 같습니다.

#include <iostream>
#include <coroutine>
#include <concepts>
#include <exception>


using namespace std;

struct cotask_noreturn {
	struct promise_type;
	using handle_type = std::coroutine_handle<promise_type>;

	struct promise_type {
		std::exception_ptr exception_;

		cotask_noreturn get_return_object() {
			return cotask_noreturn(handle_type::from_promise(*this));
		}
		std::suspend_always initial_suspend() { return {}; }
		std::suspend_always final_suspend() noexcept { return {}; }
		void unhandled_exception() { exception_ = std::current_exception(); }
		void return_void() {}
	};

	handle_type handler_;

	cotask_noreturn(handle_type handler) : handler_(handler) {}
	~cotask_noreturn()
	{
		handler_.destroy();
	}

	bool done() {
		return handler_.done();
	}

	void operator()() {
		handler_();
		if (handler_.promise().exception_)
			std::rethrow_exception(handler_.promise().exception_);
	}
};

cotask_noreturn cotask_func_a()
{
	cout << "cotask_func_a 01" << endl;
	co_await std::suspend_always();
	cout << "cotask_func_a 02" << endl;
}

cotask_noreturn cotask_func_b()
{
	cout << "cotask_func_b 01" << endl;
	co_await std::suspend_always();
	cout << "cotask_func_b 02" << endl;
}


int main(int argc, char* argv[])
{
	auto cotask0 = cotask_func_a();
	auto cotask1 = cotask_func_b();

	while (!cotask0.done() || !cotask1.done())
	{
		if (!cotask0.done())
		{
			cotask0();
		}

		if (!cotask1.done())
		{
			cotask1();
		}
	}


	return 0;
}

실행 결과는 아래와 같습니다.

cotask_func_a 01
cotask_func_b 01
cotask_func_a 02
cotask_func_b 02

 

코루틴 함수

co_await, co_return, co_yield와 같은 코루틴 관련 키워드가 들어간 함수는 C++에서 코루틴 함수로 처리됩니다. 이런 코루틴 함수는 코루틴 상태 관련 객체를 리턴해야 합니다. 코루틴 상태 관련 객체는 일정한 스펙에 맞추어 작성되어 있어야 합니다. 코루틴 상태 관련 객체는 컴파일 타임에 유효성이 검증되며 런타임 타임에 관련 함수들이 호출되어 값이 채워집니다. 코루틴 상태 관련 객체는 컴파일러에 의하여 리턴되는 코드가 만들어지는 관계로 실제로 리턴 관련 코드를 작성하지는 않습니다. 
이 예제에서는 cotask_noreturn을 상태 관련 객체로 만들어서 사용하였습니다. 

코루틴 함수 호출

auto cotask0 = cotask_func_a()으로 코루틴 함수를 호출하였을 때 함수가 실행되지는 않습니다. 함수의 실행은 코루틴 함수 내용을 담고 있는 cotask0()을 호출하였을 때 실제적으로 실행이 됩니다. 이 예제에서는 done()을 정의하여 코루틴 함수가 실행이 끝났는지를 체크하고 실행이 끝나지 않았으면 operator()() 함수를 호출하여 코루틴 함수로 제어권을 넘겨 실행합니다. 해당 인터페이스는 std 표준이 아니고 표준에 맞는 코루틴 상태 관련 클래스인 cotask_noreturn을 정의하고 해당 클래스에서 제가 정의한 인터페이스입니다. 그런 관계로 인터페이스는 본인의 취향에 따라 수정할 수 있습니다.

co_await

co_await는 쉽게 이야기하여 실행 중인 함수를 멈추고 실행 제어권을 코루틴을 호출한 쪽으로 넘깁니다. 그리고 제어권을 받아 재실행될때 이 문장 이후부터를 이어서 실행합니다. 사용법은 co_await {awaitable}로 뒤에 awaitable 객체를 사용하도록 되어 있습니다. awaitable을 커스텀으로 구현하여 co_await와 관련된 부분에 변형을 줄 수도 있지만 실제 사용하기에 awaitable 객체로 기본 라이브러리로 제공하는 std::suspend_always 객체면 충분할 듯 합니다.

반응형