Updated:

6 minute read

개요

  • 일시정지가 가능한 함수
  • 흐름
    • 호출자가 코루틴 호출
    • 코루틴은 일부 실행 후 일시중지(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