원문: Coroutine, Coroutine (Unity)
번역자: 박성국

코루틴(Coroutine)이란 무엇인가?

코루틴(Coroutine)은 컴퓨터 프로그램의 구성요소 중 하나로서, 지연될 수 있는 여러 개의 진입점을 만들어두고 특정 지점에서 해당 수행을 지속하는 방식으로 non-preemptive한 멀티태스킹 서브루틴을 일반화한 것이다. 코루틴은 협력 태스크, 예외, 이벤트 루프, 반복자, 무한 리스트나 파이프 따위의 프로그램을 구현하는데 적합하다.

도널드 누스(Donald Knuth)에 따르면, 코루틴을 만들고 여기에 코루틴이란 용어를 붙인 것은 1958년에 멜빈 콘웨이(Melvin Conway)로서 그가 코루틴을 어셈블리 프로그램를 짜는데 적용했다고 한다. 코루틴에 대한 출간된 기록은 이후 1963년에 등장하게 된다.

서브루틴과의 차이점

서브루틴은 코루틴의 특별한 경우라고 볼 수 있다. 일단 서브루틴은 호출되면 초기에 실행되고 서브루틴에서 빠져나오자(exit)마자 종료된다. 서브루틴의 인스턴스는 단 한 번만 반환(return)되며 호출 중에 특정 상태를 유지하지 않는다. 이와 반대로, 코루틴은 다른 코루틴(들)을 호출하는 식으로 exit하며, 이에 따라 다른 코루틴들은 자신을 호출한 코루틴이 호출한 지점으로 반환된다. 결국 코루틴 입장에서는 결코 빠져나오는(exit) 법이 없고 다른 코루틴을 호출할 뿐이다. 이같은 특징 때문에, 코루틴은 호출에 따라 다양한 상태를 가질 수 있다. 동시에 여러 개의 인스턴스를 가지게 되는 것도 가능하다. “yielding” 명령을 통해 다른 코루틴을 호출하는 것과 단순히 다른 루틴을 호출하는 차이는, 코루틴이 코루틴을 호출하는 경우는 둘 사이의 관계가 호출자-피호출자(caller-callee) 관계가 아니라 대칭적(symmetric)이라는 점에 있다.

yield를 호출하지 않는다면 어떤 서브루틴이든 코루틴으로 번안될(translated) 수 있다.

여기 코루틴이 유용할 수 있는 경우에 대한 예제를 포함했다. 하나의 루틴이 아이템을 생산해서 큐(queue)에 추가하고 다른 한 루틴이 해당 큐에 담긴 아이템을 제거하는 일종의 소비자-생산자 관계에 있다고 가정하자. 효율성을 위해, 프로그래머는 여러 개의 아이템을 한 번에 큐에 추가하거나 제거하고 싶다. 코드는 다음과 같이 작성될 수 있다:

var q := new queue

coroutine produce
    loop
        while q is not full
            create some new items
            add the items to q
        yield to consume

coroutine consume
    loop
        while q is not empty
            remove some items from q
            use the items
        yield to produce

여기서 큐는 다른 코루틴에 yield 명령을 통해 control을 넘기기 전에 완전히 채워지거나 비워진다. 뒤이은 코루틴은 yield 명령 직후에 시작해 바깥에서 코루틴 루프를 형성한다.

이 예제는 흔히 멀티스레딩(multithreading)을 소개할 때 쓰이기도 하지만, 해당 예제에서 두 개의 스레드가 필요하지는 않다. yield문이 하나의 코루틴에서 다른 코루틴으로 넘어가는 것을 구현하기 위해 쓰였기 때문이다.

스레드와의 차이점

코루틴은 스레드와 매우 유사하다. 그러나, 코루틴은 협력적인 멀티태스크로 연결되어 있고 이에 반해 스레드는 preemptive한 멀티태스킹의 전형이다. 이는 곧 코루틴이 동시성(concurrency를 제공하되 병렬성(parallelism)을 제공하지는 않는다는 것을 의미한다. 코루틴이 스레드에 비해 갖는 장점은 실시간 환경이 아닌 경우, 임계 지점을 방어하기 위한 뮤텍스, 세마포어 등의 동기화를 지원하기 문법을 운영 체제로부터 요구하지 않는다는 점에 있다. 이는 코루틴이 시스템 호출이나 다른 호출을 막지 않는다는 점에서 기인한다.

코루틴을 preemptively-scheduled 스레드로 구현하는 것도 가능하긴 하나, 이 경우 비-실시간 동작이나 전환 비용에서의 장점은 확보할 수 없다.

Unity에서의 코루틴

Unity 공식 문서에 따르면, “코루틴은 실행을 중지하고 control을 Unity에게 반환한 뒤 다음 프레임에 중지되었던 시점에서 다시 동작을 재개하는 함수”를 뜻한다. Unity의 C# 스크립트에서 코루틴은 다음과 같이 선언될 수 있다:

IEnumerator Fade() 
{
    for (float f = 1f; f >= 0; f -= 0.1f) 
    {
        Color c = renderer.material.color;
        c.a = f;
        renderer.material.color = c;
        yield return null;
    }
}

이에 따르면, Unity에서의 코루틴은 IEnumerator형의 반환 형식을 가지고 body에 yield 반환문을 가지도록 선언된 함수에 해당한다. yield가 반환되는 줄은 실행이 중지되고 그 동작이 다음 프레임에 재개되는 지점에 해당한다. Unity에서 코루틴을 실행하기 위해서는 StartCoroutine 함수를 사용해야만 한다:

void Update()
{
    if (Input.GetKeyDown("f")) 
    {
        StartCoroutine("Fade");
    }
}

주의점: 코루틴은 MonoBehaviour가 비활성화되더라도 MonoBehaviour가 완전히 destroy되기 전까지는 중단되지 않는다. 프로그래머는 Unity 상에서 코루틴을 MonoBehaviour.StopCoroutine 또는 MonoBehaviour.StopAllCoroutines을 호출하는 식으로 중단시킬 수 있다. 또한 코루틴은 MonoBehaviour가 destroy될 때 중단된다.

<계속>