🌀 코루틴(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 포인트 |
반응형
'프로그래밍 > C,C++' 카테고리의 다른 글
[C++] 반환값 최적화(RVO, NRVO) (0) | 2025.05.14 |
---|---|
[Modern C++20] 🚀주요 특징 & 설명 (0) | 2025.04.16 |
[Modern C++20] concept (0) | 2025.04.08 |
[Modern C++] string_view (0) | 2025.04.02 |
[Modern C++20] 삼중 비교 연산자(Three-Way Comparison) (0) | 2025.03.28 |