개요
- 일시정지가 가능한 함수
- 흐름
- 호출자가 코루틴 호출
- 코루틴은 일부 실행 후 일시중지(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 반환
- 구성
- promise object
- coroutine handle
- coroutine 외부에서 조작
- 코루틴을 재개하거나 파괴
- coroutine state
- promise object
- 매개변수(copy by value)
- 일시정지 지점
- 일시정지 지점에서의 로컬 및 임시 변수
- 동작
- 최초 실행
- 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 내용 실행
- 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 유지에 필요한 정보는 힙 메모리에 할당
예제
- 기본
- 코드
#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