← SEARCHABLE ARCHIVE
C / C++ · 2026-08-15

C++ 기초부터 마스터까지 — 21화

Coroutine의 중단, 재개, 수명과 취소 이해하기

INTERACTIVE READING MODE →

본 블로그 작성 글들은, 강의를 목표로 하고 제작되었습니다. 또한 작성된 글의 정보도 틀렸을 수 있으니, 주의해주세요.

Coroutine은 함수 실행을 중간에 멈추고 나중에 같은 상태에서 다시 시작할 수 있게 한다. 스레드를 자동으로 만들거나 비동기 I/O를 제공하는 기능이 아니라 비동기 제어 흐름을 구현할 언어 수준의 변환 규칙이다.

함수의 상태가 Coroutine frame에 저장된다

Coroutine에서 co_await, co_yield, co_return을 사용하면 컴파일러는 지역 변수와 재개 위치를 보존하는 상태 기계로 함수를 변환한다.

Task<Texture> LoadTextureAsync(Path path)
{
    FileBytes bytes = co_await ReadFileAsync(path);
    DecodedImage image = co_await DecodeAsync(std::move(bytes));
    co_return UploadTexture(std::move(image));
}

위 Task와 awaitable은 표준 라이브러리가 자동으로 제공하는 타입이 아니라 프로젝트나 비동기 라이브러리가 정의해야 하는 계약이다. 표준 C++20은 Coroutine 변환 규칙을 제공하지만 범용 executor와 네트워크 event loop까지 정하지 않는다.

co_await는 중단 여부를 awaitable에 묻는다

개념적으로 awaitable은 세 단계를 제공한다.

await_ready   지금 결과를 사용할 수 있는가?
await_suspend 중단할 때 누가 완료를 알리고 다시 시작하는가?
await_resume  재개한 뒤 어떤 값이나 오류를 돌려주는가?

작업이 이미 끝났다면 중단하지 않고 계속 실행할 수 있다. 중단한다면 Coroutine handle을 scheduler나 I/O 시스템에 안전하게 넘기고 완료 시 정확히 한 번 재개해야 한다.

frame의 소유자와 수명이 핵심이다

호출자가 반환된 Task를 바로 버렸을 때 작업을 취소할지 계속 실행할지 정해야 한다. 대기 중인 I/O callback이 이미 파괴된 frame을 재개하면 use-after-free가 된다.

Coroutine에 참조로 전달한 객체도 중단 중에 사라질 수 있다.

Task<void> ShowLater(const Player& player)
{
    co_await NextFrame();
    Draw(player.Name); // player가 아직 살아 있는가?
}

값을 frame에 소유하게 복사하거나, 안정적인 handle을 사용하거나, 호출자가 수명을 보장하는 명시적 계약이 필요하다.

취소와 오류도 프로토콜이다

취소 요청은 대기 중 작업을 깨우고, frame을 재개해 정리 코드를 실행한 뒤, 상위 Task로 취소 결과를 전달해야 한다. 예외를 Task 안에 저장했다가 co_await하는 위치에서 다시 던질지, 오류 값으로 반환할지도 promise type이 결정한다.

Coroutine 코드는 순차 코드처럼 읽히지만 실행은 여러 시점에 나뉜다. frame 소유권, executor, 재개 스레드, 취소, 오류 전달을 먼저 정하고 라이브러리의 Task 계약을 문서와 테스트로 확인해야 한다.