C++20 Coroutines — co_await·co_yield·co_return 내부 메커니즘
개요 — C++20 Coroutines
Section titled “개요 — C++20 Coroutines”C++20 Coroutines는 일시 중단(suspend)과 재개(resume)가 가능한 함수를 언어 차원에서 지원합니다. 함수가 중간에 멈추고, 나중에 멈춘 지점부터 다시 실행할 수 있어 Generator, 비동기 I/O, 상태 머신 구현에 매우 적합합니다.
코루틴 함수의 특징:
- 함수 본문에
co_await,co_yield,co_return중 하나 이상 포함 - 반환 타입은 반드시 Promise 타입 프로토콜을 구현해야 함
- 호출 시 즉시 실행되지 않고 코루틴 프레임(Coroutine Frame)이 힙에 할당됨
1. 세 가지 핵심 키워드
Section titled “1. 세 가지 핵심 키워드”| 키워드 | 역할 |
|---|---|
co_await expr | expr의 Awaitable이 ready가 아니면 코루틴 일시 중단 |
co_yield value | 값을 호출자에게 전달하고 일시 중단 |
co_return value | 코루틴 최종 완료, 반환값 설정 |
// co_yield 사용 예 — Generator 스타일Generator<int> Fibonacci(){ int a = 0, b = 1; while (true) { co_yield a; // a를 호출자에게 전달하고 중단 int tmp = a + b; a = b; b = tmp; }}
// co_await 사용 예 — 비동기 태스크Task<std::string> FetchData(std::string url){ auto response = co_await HttpGet(url); // 완료될 때까지 중단 auto parsed = co_await ParseJson(response); co_return parsed["name"]; // 최종값 반환}2. 코루틴 내부 구조 — Promise 타입
Section titled “2. 코루틴 내부 구조 — Promise 타입”모든 코루틴은 반환 타입에 연결된 promise_type 이너 클래스를 통해 동작합니다.
#include <coroutine>
// 간단한 Generator 구현template<typename T>struct Generator{ // 필수 inner 타입: promise_type struct promise_type { T current_value;
// 코루틴 객체 생성 Generator get_return_object() { return Generator{ std::coroutine_handle<promise_type>::from_promise(*this) }; }
// 코루틴 시작 시 즉시 중단? (suspend_always = true) std::suspend_always initial_suspend() { return {}; }
// 코루틴 완료 후 중단? std::suspend_always final_suspend() noexcept { return {}; }
// co_yield 처리 std::suspend_always yield_value(T value) { current_value = value; return {}; // 항상 중단 }
// co_return; (void) void return_void() {}
// 예외 처리 void unhandled_exception() { std::terminate(); } };
// 코루틴 핸들 — 재개/소멸 제어 std::coroutine_handle<promise_type> handle;
explicit Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
~Generator() { if (handle) handle.destroy(); }
// 다음 값 가져오기 bool MoveNext() { handle.resume(); return !handle.done(); }
T Current() const { return handle.promise().current_value; }};3. Generator 활용 예시
Section titled “3. Generator 활용 예시”Generator<int> Range(int start, int end, int step = 1){ for (int i = start; i < end; i += step) co_yield i;}
Generator<int> Fibonacci(){ int a = 0, b = 1; while (true) { co_yield a; auto tmp = a + b; a = b; b = tmp; }}
// 사용auto gen = Range(0, 10, 2);while (gen.MoveNext()) std::cout << gen.Current() << " "; // 0 2 4 6 8
auto fib = Fibonacci();for (int i = 0; i < 10 && fib.MoveNext(); ++i) std::cout << fib.Current() << " "; // 0 1 1 2 3 5 8 13 21 344. Awaitable 인터페이스
Section titled “4. Awaitable 인터페이스”co_await expr를 만나면 컴파일러는 expr의 Awaitable 프로토콜을 호출합니다.
// Awaitable이 되기 위한 세 가지 멤버 함수struct MyAwaitable{ // 이미 완료됐는가? true면 중단 없이 즉시 계속 실행 bool await_ready() const noexcept { return false; }
// 중단 직전 호출 — 재개 로직(콜백 등록 등) 수행 // handle: 현재 코루틴의 핸들 — 재개하려면 handle.resume() 호출 void await_suspend(std::coroutine_handle<> handle) { // 예: 비동기 I/O 완료 콜백에서 handle.resume() 예약 some_async_operation([handle]() mutable { handle.resume(); }); }
// 재개 후 co_await 표현식의 최종 결과값 int await_resume() const noexcept { return 42; }};
// 사용Task<void> Example(){ int result = co_await MyAwaitable{}; // result = 42}내장 Awaitable
Section titled “내장 Awaitable”| 타입 | 의미 |
|---|---|
std::suspend_always | 항상 중단 (await_ready() = false) |
std::suspend_never | 절대 중단 안 함 (await_ready() = true) |
5. Task — 비동기 코루틴 반환 타입
Section titled “5. Task — 비동기 코루틴 반환 타입”#include <coroutine>#include <exception>
// 단순 Task<T> 구현 (실제 프로덕션은 더 복잡)template<typename T>struct Task{ struct promise_type { T result; std::exception_ptr exception;
Task get_return_object() { return Task{std::coroutine_handle<promise_type>::from_promise(*this)}; }
std::suspend_never initial_suspend() { return {}; } // 즉시 실행 std::suspend_always final_suspend() noexcept { return {}; }
void return_value(T value) { result = std::move(value); }
void unhandled_exception() { exception = std::current_exception(); } };
std::coroutine_handle<promise_type> handle;
explicit Task(std::coroutine_handle<promise_type> h) : handle(h) {} ~Task() { if (handle) handle.destroy(); }
T Get() { if (handle.promise().exception) std::rethrow_exception(handle.promise().exception); return handle.promise().result; }};6. 코루틴 실행 흐름 다이어그램
Section titled “6. 코루틴 실행 흐름 다이어그램”호출자 코루틴 함수 본문 | | |--- 코루틴 생성 ------------>| | initial_suspend() |<-- 제어권 반환 -------------| | |--- handle.resume() -------->| | [실행] | co_yield v |<-- 값 전달 + 중단 ----------| | |--- handle.resume() -------->| | [재개] | co_return result | final_suspend() |<-- 완료 -------------------| | |--- handle.destroy() ------->| (소멸자에서 자동 호출)7. 코루틴 프레임 (Coroutine Frame)
Section titled “7. 코루틴 프레임 (Coroutine Frame)”코루틴이 처음 호출되면 컴파일러는 다음 정보를 힙에 할당합니다:
// 컴파일러가 내부적으로 생성하는 코루틴 프레임 (의사 코드)struct CoroutineFrame{ // Promise 객체 promise_type promise;
// 현재 중단 포인트 (재개 위치를 결정하는 상태 변수) int suspend_index;
// 코루틴 본문에서 사용된 지역 변수들 T local_var1; U local_var2; // ...
// resume/destroy 함수 포인터 void (*resume_fn)(CoroutineFrame*); void (*destroy_fn)(CoroutineFrame*);};이 때문에 코루틴은 힙 할당 비용이 있으며, 컴파일러 최적화(Heap Allocation Elision)가 적용될 경우 스택으로 대체되기도 합니다.
8. 실전 활용 패턴
Section titled “8. 실전 활용 패턴”상태 머신 구현
Section titled “상태 머신 구현”Generator<std::string> StateMachine(){ co_yield "초기화 중..."; co_yield "데이터 로딩 중..."; co_yield "처리 중..."; co_yield "완료";}
auto sm = StateMachine();while (sm.MoveNext()){ std::cout << sm.Current() << "\n"; // 각 상태에서 UI 업데이트, 이벤트 처리 등 수행}재귀 없는 트리 순회
Section titled “재귀 없는 트리 순회”struct TreeNode { int value; TreeNode* left; TreeNode* right; };
Generator<int> InOrder(TreeNode* node){ if (!node) co_return; // 재귀 호출 대신 co_yield from 패턴 (직접 지원 없어 수동 체이닝) auto left = InOrder(node->left); while (left.MoveNext()) co_yield left.Current(); co_yield node->value; auto right = InOrder(node->right); while (right.MoveNext()) co_yield right.Current();}9. 주의사항과 한계
Section titled “9. 주의사항과 한계”| 항목 | 내용 |
|---|---|
| 힙 할당 | 코루틴 프레임은 기본적으로 힙에 할당됨 |
| 수명 관리 | 코루틴 핸들 미소멸 시 메모리 누수 |
| 표준 라이브러리 부재 | C++20에는 Task, Generator 표준 타입 없음 (C++23에 std::generator 추가) |
| 예외 전파 | unhandled_exception()을 반드시 구현해야 함 |
| 스레드 안전 | 코루틴 자체는 스레드 안전 보장 없음 — 동기화 직접 구현 필요 |
10. C++23 std::generator
Section titled “10. C++23 std::generator”#include <generator> // C++23
std::generator<int> Fib(){ auto [a, b] = std::pair{0, 1}; while (true) { co_yield a; std::tie(a, b) = std::pair{b, a + b}; }}
// Ranges와 완벽 통합for (int n : Fib() | std::views::take(10)) std::cout << n << " ";11. 정리
Section titled “11. 정리”| 구분 | 내용 |
|---|---|
co_yield | 값 전달 + 중단 — Generator 패턴 |
co_await | 비동기 대기 — Task/Future 패턴 |
co_return | 최종 값 반환 및 코루틴 완료 |
| promise_type | 코루틴 동작 정의 — 직접 구현 또는 라이브러리 사용 |
| Awaitable | await_ready/suspend/resume 세 함수 구현 |
C++20 Coroutines는 강력하지만 로우레벨 인터페이스입니다. 실무에서는 cppcoro, Asio, Folly와 같은 라이브러리가 제공하는 Task, Generator 타입을 사용하거나, C++23 std::generator를 활용하는 것이 권장됩니다.