프로그래밍/C,C++

[Modern C++20] coroutine

GONII 2025. 4. 11. 11:26

🌀 코루틴(Coroutine)이란?

"함수를 중간에 멈췄다가, 나중에 다시 이어서 실행할 수 있는 함수"

즉, co_await, co_yield, co_return 같은 키워드를 통해
비동기 흐름이나 게으른 계산을 간단하게 표현할 수 있게 해주는 기능


🔍 왜 코루틴을 쓰는 걸까?

  • 콜백 지옥 없이 비동기 코드를 쉽게 표현 가능 (비동기 흐름을 ‘선형’ 코드처럼 표현할 수 있는)
  • 지연 평가(lazy evaluation) 처리 가능 (ex. 제너레이터)
  • 상태 머신을 코루틴 하나로 표현 가능

✨ 간단 예제 1: co_yield 제너레이터

#include <iostream>
#include <coroutine>
#include <generator> // GCC에서 <experimental/generator> 또는 라이브러리 필요

std::generator<int> count_to_3() {
    co_yield 1;
    co_yield 2;
    co_yield 3;
}

int main() {
    for (int i : count_to_3()) {
        std::cout << i << " ";
    }
}

💡 co_yield는 값을 하나씩 생산(Generate) 하는 코루틴이야.


✨ 예제 2: co_await 비동기 스타일

#include <iostream>
#include <coroutine>
#include <thread>
#include <chrono>

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

    struct promise_type {
        Task get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
    };
};

Task do_something() {
    std::cout << "1. Start\n";
    co_await std::suspend_always{};
    std::cout << "2. Resume\n";
}

int main() {
    auto handle = do_something();  // 실행은 여기까지만 됨 (Start 출력)
    // 이후 resume() 등을 통해 다시 실행 가능
}
  • co_await std::suspend_always{} 는 일시정지
  • std::coroutine_handle<>::resume() 호출로 다시 실행

🎮 게임에서 C++ 코루틴 활용 예시


1️⃣ 비동기 로딩 시스템

🕹️ 상황:

"게임에서 다음 레벨 리소스를 백그라운드로 로딩하고, 로딩 끝나면 자동으로 다음 씬으로 전환"

🧠 코루틴 없을 때:

if (ResourceManager::isLoaded("next_scene")) {
    changeScene("next_scene");
}

이건 계속 체크해야 하고, 상태 관리가 지저분해짐.

✅ 코루틴 사용 시:

co_await ResourceManager::loadAsync("next_scene");
changeScene("next_scene");  // 깔끔하게 이어짐!

장점: 상태 머신 필요 없이 동기 코드처럼 비동기 흐름 표현 가능


2️⃣ 애니메이션 시퀀스 / 시간 지연 처리

🕹️ 상황:

"몬스터가 공격 → 1.5초 후에 데미지 입히기"

co_await waitForSeconds(1.5);
monster->dealDamage(10);
  • waitForSeconds()는 내부적으로 타이머 + 코루틴 suspend로 구현
  • Unity의 yield return new WaitForSeconds()랑 비슷한 느낌!

장점: 코드 가독성 폭발 + 동작 흐름 그대로 작성 가능


3️⃣ 네트워크 통신 처리 (서버/클라 모두)

🧠 예: 클라가 서버에 데이터 요청 후 응답 기다림

Response res = co_await sendRequestAsync("get_player_data");
handlePlayerData(res);
  • 기존에는 콜백이나 상태 플래그 + 반복 체크가 필요했음
  • 이제는 그냥 동기처럼 작성 가능

🖥️ 서버에서 C++ 코루틴 활용 예시


1️⃣ 비동기 I/O (파일, 네트워크)

auto data = co_await async_read_file("user_save.json");
  • 비동기 소켓 통신도 마찬가지:
std::string message = co_await async_read(socket);

epoll, io_uring 같은 저수준 비동기 I/O를 코루틴으로 감싸면, 성능 + 코드 가독성 둘 다 챙김!


2️⃣ Coroutine 기반 Task 시스템 구현

  • 서버에서 수천 개의 요청을 std::coroutine_handle<>로 관리
  • task_pool, event_loop, suspend/resume로 부하를 나눠 처리
  • 유사 예: libcoro, cppcoro

3️⃣ 게임 서버에서 유저별 상태 머신

co_await player.wait_for_action("attack");
handle_attack();
  • 턴 기반 게임이나 채팅 서버처럼 유저 상태가 순차적으로 흐를 때 강력함
  • FSM을 코루틴으로 깔끔하게 표현 가능

⚙️ 코루틴이 게임/서버에서 좋은 이유

이점 설명
✅ 동기처럼 보이는 비동기 코드 코드 가독성 & 유지보수 향상
✅ 상태 머신 생략 가능 복잡한 흐름도 한 줄씩 처리
✅ IO 효율 향상 수천 개 코루틴도 가볍게 처리 가능 (스레드 아님!)
✅ 구조화된 흐름 제어 callback 지옥 방지

🔥 실제 적용 사례

  • Unity C#: 예전부터 yield, async/await로 코루틴 사용 중
  • Unreal Engine: Latent Action, Timer 기반 → C++20 코루틴 도입 가능성↑
  • 서버 엔진 (예: custom): cppcoro, boost::asio, liburing 등과 코루틴 연동

🧩 코루틴 핵심 구성요소

요소 설명
co_await awaitable 객체를 기다림 (비동기 처리)
co_yield 하나씩 값을 반환 (제너레이터 용)
co_return 코루틴 종료 시 값 반환
promise_type 코루틴 동작 정의 클래스 (필수)
std::coroutine_handle 코루틴 상태를 제어하는 핸들

🔧 코루틴 사용하는 패턴

패턴 예시
제너레이터 co_yield로 값 하나씩 생성
비동기 Task co_await로 비동기 흐름 구현
상태 머신 내부적으로 상태 저장 가능 (ex. 반복기, FSM)

📦 라이브러리 예시

  • cppcoro (비동기 Task, generator 등)
  • GSL의 generator
  • C++23에는 표준 라이브러리에 generator, task, async가 더 추가될 예정

⚠️ 주의할 점

  • 코루틴은 복잡한 구조 (promise_type 등)를 필요로 해서 직접 구현보다는 라이브러리 활용 권장
  • 아직 일부 표준 라이브러리는 완전히 지원되지 않음 (예: Visual Studio는 std::generator 미지원 → cppcoro 등 사용)

🎯 요약 정리

항목 내용
도입 버전 C++20
키워드 co_await, co_yield, co_return
대표 용도 비동기 코드, 지연 평가, 제너레이터
장점 간결하고 읽기 쉬운 비동기 코드 작성
핵심 요소 promise_type, coroutine_handle, suspend 포인트

 

 

반응형