코루틴(coroutine)은 비선점 멀티태스킹을 위해 서브루틴을 일반화 한 것…이라고 한다. 제너레이터(generator)와 가까운 친척, 고루틴(goroutine)과도 미묘한 친척. 핵심은 지속하는 실행 문맥(스택 + 현재 실행 위치 + 기타 레지스터 + …)들 사이를 오가며 실행하는 것이다.

운영체제가 “그만! 이번엔 거기까지-“라며 강제로 문맥을 전환하는 식이 아니라 프로그램에서 자발적으로 다른 루틴에게 양보(yield)하니까 비선점이다. 운영체제에 들르지 않아도 되니 문맥 전환이 효율적이고 스케줄러가 없는 환경(bare metal 등)에서 이용할 수 있…을지도 모른다.

실행을 양보하는 즉시 실행 문맥이 사라진다는 제약을 추가하면 서브루틴이 된다. 루틴이 한 번 돌면 사라질 실행 문맥을 굳이 계속 유지할 필요가 없고, 그래서 서브루틴을 부를 때는 caller의 스택에 프레임을 하나 쌓아서 callee를 위한 임시 문맥을 만들었다가 되돌아 올 때 없앤다.

멀티태스킹이래도 결국 한 CPU를 돌려 쓰는 거니까 여러 루틴이 동시에 실행(concurrency)되기는 하지만 병렬로 실행(parallelism)되는 건 아니다. 그리고 실행 흐름 넘기는 지점을 직접 프로그래밍 해야 하니 복잡한 프로그램에서 여기저기 쓰기에는 골치 아플 수 있다.

C에도 코루틴이 있으면 좋겠지만 저수준 언어한테 그런 거 요구하면 안 된다. 하지만 코루틴이란 게 결국은 실행 문맥 만들고 전환할 수 있으면 되는 거라서 요래조래 비슷하게 만들 수는 있다.

코루틴보다 살짝 더 직관적인 게 제너레이터니까 다음 파이썬 코드를:

def fib(n):
    a, b = 0, 1
    for i in xrange(n):
        yield a
        a, b = b, a+b

for i in fib(10):
    print i

C로 대강 흉내내면 이런 식이다.

#include <stdio.h>
#include <ucontext.h>

static ucontext_t uctx_main, uctx_fib;
static char uctx_fib_stack[1024];

static struct fib_arg {
    /* in args */
    int n;

    /* out result */
    int generated;
    int ret;
} fib_arg;

static void
fib(void)
{
    int i, n;
    int a = 0, b = 1;

    fib_arg.generated = 1;

    for (i = 0; i < fib_arg.n; i++) {
        fib_arg.ret = a;
        swapcontext(&uctx_fib, &uctx_main);

        n = a + b;
        a = b;
        b = n;
    }

    fib_arg.generated = 0;
}

int
main(void)
{
    getcontext(&uctx_fib);
    uctx_fib.uc_stack.ss_sp = uctx_fib_stack;
    uctx_fib.uc_stack.ss_size = sizeof(uctx_fib_stack);
    uctx_fib.uc_link = &uctx_main;
    makecontext(&uctx_fib, fib, 0);

    fib_arg.n = 10;
    while (swapcontext(&uctx_main, &uctx_fib),
           fib_arg.generated)
        printf("%d\n", fib_arg.ret);

    return 0;
}

너저분하다. 캡슐화 잘 하면 좀 더 깔끔해지겠지만 이식성 우려 때문에 POSIX에서 퇴출된 함수를 가지고 그렇게 애쓸 필요까지야.

개념적으로 makecontext())/setcontext()setjmp()/longjmp()를 일반화 한 거라고 볼 수도 있다. 스택을 되감는 쪽으로만 문맥을 바꿀 수 있느냐 더 자유롭게 전환할 수 있느냐의 차이. 핵심은 결국 주요 레지스터들을 저장하거나 복원하는 거라서 glibc의 소스 코드를 봐도 꽤 짧고 단순하다.