[C++] coroutines
Updated:
개요
- 일시정지가 가능한 함수
- 흐름
- 호출자가 코루틴 호출
- 코루틴은 일부 실행 후 일시중지(suspend)하고 리턴
- 호출자가 재개(resumed)하면 일시중지 된 부분부터 실행
- 일시중지와 재개를 반복
- 파괴(destroy)
- 제한
- 가변 인자, plain return 문, placeholder return 타입(auto or concept) 사용 불가
- main/constexpr/Consteval 함수, 생성자, 소멸자는 coroutine 불가
- coroutine이 되려면 co_await, co_yield, co_return 중 하나는 사용하여야 함
- co_await
- 실행을 일시중지
co_await expr
- expr은 awaitable이며 custom 가능
- co_yield
- 값을 반환하면서 실행을 일시중지
- co_return
- 값을 반환하면서 실행 완료
- co_return 이후에는 재개 불가
- done()
- coroutines이 완료되면 true, 아니면 false 반환
- co_await
- 구성
- promise object
- 해당 객체를 통해 결과 또는 예외를 제출
- coroutine handle
- coroutine 외부에서 조작
- 코루틴을 재개하거나 파괴
- coroutine state
- promise object
- 매개변수(copy by value)
- 일시정지 지점
- 일시정지 지점에서의 로컬 및 임시 변수
- promise object
- 동작
- 최초 실행
- new 연산자를 사용하여 coroutine state 객체 할당
- 매개변수를 coroutine state에 복사
- 값 매개변수는 이동 또는 복사되고 참조 매개변수는 참조로 남음
- 참조 객체의 수명이 끝난 후 coroutine이 재개되는 상황 주의
- promise object의 생성자 호출
- promise.get_return_object()를 호출하고 결과를 로컬 변수에 유지 후 처음 일시중지 시 호출자에게 반환
- promise.initial_suspend() 호출하고 결과를 co_await에 전달
- lazily-started coroutine은 suspend_always를 반환
- suspend_always는 호출자로 제어권 넘김
- eagerly-started coroutine은 suspend_never를 반환
- suspend_never는 호출자로 제어권 넘기지 않고 바로 coroutine 내용 실행
- lazily-started coroutine은 suspend_always를 반환
- co_await/co_yield
- coroutine 리턴 타입으로 암시적 변환 후 호출자/재개자에게 반환
- co_return
co_return expr
에서 expr이 void면 promise.return_void() 아니면 promise.return_value(expr) 호출- return_void와 return_value는 동시 구현 불가
- promise.final_suspend() 호출하고 결과를 co_await에 전달
- 예외 발생
- promise.unhandled_exception() 호출
- promise.final_suspend() 호출하고 결과를 co_await에 전달
- promise object 소멸자 호출
- 함수 매개변수 복사본 소멸자 호출
- delete 연산자를 이용하여 coroutine state 소멸
- 최초 실행
- C++ coroutine은 stackless coroutine
- stackful coroutine
- coroutine 호출 시 자신만의 스택을 지님
- coroutine의 라이프사이클은 호출자로부터 독립적
- stackless coroutine
- 호출자의 스택을 사용
- coroutine의 라이프사이클은 호출자의 라이프사이클을 따름
- coroutine state 유지에 필요한 정보는 힙 메모리에 할당
- stackful coroutine
예제
- 기본
- 코드
#include <coroutine> #include <future> #include <iostream> #include <memory> using namespace std; class Task1 { public: struct promise_type { Task1 get_return_object() { return Task1{ coroutine_handle<promise_type>::from_promise(*this)}; } auto initial_suspend() { return suspend_always{}; } auto final_suspend() noexcept { return suspend_always{}; } void return_void() {} void unhandled_exception() {} }; coroutine_handle<promise_type> handler; Task1(coroutine_handle<promise_type> handler) : handler(handler) { cout << "~~~~~~" << endl; } ~Task1() { this->handler.destroy(); } }; class Task2 { public: struct promise_type { int value; Task2 get_return_object() { return Task2{ coroutine_handle<promise_type>::from_promise(*this)}; } auto initial_suspend() { return suspend_never{}; } auto final_suspend() noexcept { return suspend_always{}; } void return_value(int value) { this->value = value; } void unhandled_exception() {} }; coroutine_handle<promise_type> handler; Task2(coroutine_handle<promise_type> handler) : handler(handler) {} ~Task2() { this->handler.destroy(); } }; class Task3 { public: struct promise_type { int value = 0; Task3 get_return_object() { return Task3{ coroutine_handle<promise_type>::from_promise(*this)}; } auto initial_suspend() { return suspend_always{}; } auto final_suspend() noexcept { return suspend_always{}; } void return_void() {} void unhandled_exception() {} suspend_always yield_value(int value) { cout << "!!!" << endl; this->value = value; return {}; } }; coroutine_handle<promise_type> handler; Task3(coroutine_handle<promise_type> handler) : handler(handler) {} ~Task3() { this->handler.destroy(); } }; Task1 func1() { cout << " " << __func__ << " " << 1 << endl; co_await suspend_always{}; cout << " " << __func__ << " " << 2 << endl; co_return; } Task2 func2() { cout << " " << __func__ << " " << 1 << endl; co_await suspend_always{}; cout << " " << __func__ << " " << 2 << endl; co_return 7; } Task1 func3() { cout << " " << __func__ << " " << 1 << endl; co_await suspend_never{}; cout << " " << __func__ << " " << 2 << endl; co_return; } Task3 func4() { cout << " " << __func__ << " " << 1 << endl; co_yield 7; cout << " " << __func__ << " " << 2 << endl; co_return; } void test_func1() { cout << __func__ << " " << 1 << endl; Task1 task1 = func1(); cout << __func__ << " " << 2 << endl; task1.handler.resume(); cout << __func__ << " " << 3 << endl; cout << __func__ << " " << 4 << ", done : " << task1.handler.done() << endl; task1.handler.resume(); cout << __func__ << " " << 5 << ", done : " << task1.handler.done() << endl; } void test_func2() { cout << __func__ << " " << 1 << endl; Task2 task2 = func2(); cout << __func__ << " " << 2 << endl; cout << __func__ << " " << 3 << ", done : " << task2.handler.done() << endl; task2.handler.resume(); cout << __func__ << " " << 4 << ", done : " << task2.handler.done() << endl; cout << __func__ << " " << 5 << ", value : " << task2.handler.promise().value << endl; } void test_func3() { cout << __func__ << " " << 1 << endl; Task1 task1 = func3(); cout << __func__ << " " << 2 << endl; cout << __func__ << " " << 3 << ", done : " << task1.handler.done() << endl; task1.handler.resume(); cout << __func__ << " " << 4 << ", done : " << task1.handler.done() << endl; } void test_func4() { cout << __func__ << " " << 1 << endl; Task3 task3 = func4(); cout << __func__ << " " << 2 << ", value : " << task3.handler.promise().value << endl; task3.handler.resume(); cout << __func__ << " " << 3 << ", value : " << task3.handler.promise().value << endl; task3.handler.resume(); cout << __func__ << " " << 4 << ", value : " << task3.handler.promise().value << endl; } int main() { test_func1(); cout << endl << "------" << endl << endl; test_func2(); cout << endl << "------" << endl << endl; test_func3(); cout << endl << "------" << endl << endl; test_func4(); return 0; }
- 실행 결과
test_func1 1 ~~~~~~ test_func1 2 func1 1 test_func1 3 test_func1 4, done : 0 func1 2 test_func1 5, done : 1 ------ test_func2 1 func2 1 test_func2 2 test_func2 3, done : 0 func2 2 test_func2 4, done : 1 test_func2 5, value : 7 ------ test_func3 1 ~~~~~~ test_func3 2 test_func3 3, done : 0 func3 1 func3 2 test_func3 4, done : 1 ------ test_func4 1 test_func4 2, value : 0 func4 1 !!! test_func4 3, value : 7 func4 2 test_func4 4, value : 7
- 코드
- custom awaitable
- 다른 쓰레드에서 재개
- 코드
#include <coroutine> #include <future> #include <iostream> #include <memory> #include <thread> using namespace std; class Task { public: struct promise_type { Task get_return_object() { return Task{ coroutine_handle<promise_type>::from_promise(*this)}; } auto initial_suspend() { return suspend_always{}; } auto final_suspend() noexcept { return suspend_always{}; } void return_void() {} void unhandled_exception() {} }; coroutine_handle<promise_type> handler; ~Task() { this->handler.destroy(); } }; struct awaitable { constexpr bool await_ready() const noexcept { return false; } void await_suspend(coroutine_handle<> handle) const noexcept { future<void> f = async(launch::async, [handle]() { cout << " async : " << this_thread::get_id() << endl; handle.resume(); }); f.get(); } constexpr void await_resume() const noexcept {} }; Task func() { cout << " " << __func__ << " " << 1 << ", thread id : " << this_thread::get_id() << endl; co_await awaitable{}; cout << " " << __func__ << " " << 2 << ", thread id : " << this_thread::get_id() << endl; co_return; } void test_func() { cout << __func__ << " " << 1 << endl; Task task = func(); cout << __func__ << " " << 2 << ", done : " << task.handler.done() << endl; task.handler.resume(); cout << __func__ << " " << 3 << ", done : " << task.handler.done() << endl; } int main() { test_func(); return 0; }
- 실행 결과
test_func 1 test_func 2, done : 0 func 1, thread id : 140193857099584 async : 140193849931328 func 2, thread id : 140193849931328 test_func 3, done : 1