코루틴은 함수나 루틴의 실행을 중단(suspend)하고 필요에 따라 다시 재개(resume)할 수 있는 프로그래밍 구조입니다. 일반적인 메서드는 호출되면 실행을 완료한 뒤 호출한 곳에 제어를 반환하지만, 코루틴은 실행 중간에 yield
와 같은 특정 키워드를 사용하여 실행을 일시 정지하고 제어를 다른 곳에 넘겼다가 나중에 중단한 부분부터 실행을 이어갈 수 있습니다.
코루틴의 이름은 '함께(co-)'와 '루틴(routine)'의 합성어로, 여러 루틴이 협력적(cooperative)으로 실행을 제어하는 프로그래밍 패턴을 의미합니다. 이 개념은 1960년대에 처음 소개되었습니다.
코루틴은 다음과 같은 특징을 가집니다:
코루틴과 스레드는 모두 동시성 프로그래밍을 지원하지만, 동작 방식과 설계 철학이 다릅니다.
특징 | 코루틴(Coroutine) | 스레드(Thread) |
---|---|---|
제어 방식 | 협력적 제어(Cooperative Scheduling) | 선점형 제어(Preemptive Scheduling) |
실행 단위 | 애플리케이션 레벨에서 실행 흐름 관리 | 운영체제 레벨에서 실행 흐름 관리 |
컨텍스트 전환 비용 | 낮음 (스택 스위칭 필요 없음) | 높음 (스택과 레지스터 컨텍스트 전환 필요) |
병렬성 | 단일 스레드 내에서 동작 (병렬 실행 불가능) | 다중 코어를 활용하여 병렬 실행 가능 |
상태 관리 | 상태는 애플리케이션 코드에서 관리 | 상태는 운영체제가 관리 |
수량 | 한 스레드에서 수천 개 실행 가능 | 운영체제 및 하드웨어 자원에 제한됨 |
오버헤드 | 적음 (가벼움) | 높음 (스레드 스택, 스케줄링 비용) |
중요한 점은 코루틴은 스레드가 아니라는 것입니다. 코루틴은 비동기적이고 협력적인 반면, 스레드는 운영체제에서 제공하는 선점형 동시성을 기반으로 합니다. 코루틴의 동기 작업은 여전히 메인 스레드에서 실행됩니다. 메인 스레드의 CPU 시간을 줄이려면 코루틴에서도 다른 스크립트 코드와 마찬가지로 작업 차단을 방지하는 것이 중요합니다. Unity에서 다중 스레드 코드를 사용하려면 C# 잡 시스템을 고려할 수 있습니다.
코루틴과 스레드는 서로 대체되는 개념이라기보다는 상호 보완적으로 사용됩니다. 대부분의 코루틴 프레임워크는 코루틴이 스레드 위에서 실행되도록 설계되어 있으며, 하나의 스레드에서 다수의 코루틴을 실행하여 자원 사용을 최적화합니다. 스레드는 병렬성(parallelism)에, 코루틴은 동시성(concurrency)에 적합합니다. 예를 들어, 멀티 코어 시스템에서 스레드를 병렬로 실행하고 각 스레드 내에서 코루틴을 사용하여 동시성을 처리할 수 있습니다.
일반적으로 I/O 중심 작업이나 대기 시간이 많은 작업은 코루틴이 더 적합하며, CPU 중심 작업이나 병렬 처리는 스레드가 더 적합합니다. 현대 프로그래밍에서는 이 두 개념을 상호 보완적으로 활용하여 높은 성능과 효율성을 달성합니다.
Unity에서 코루틴은 작업을 다수의 프레임에 분산할 수 있게 해줍니다. 시간의 흐름에 따른 이벤트의 시퀀스나 절차적인 애니메이션을 구현하는 데 유용합니다. HTTP 전송, 에셋 로드, 파일 I/O 완료 등을 기다리는 긴 비동기 작업을 처리할 때 사용하는 것이 가장 좋습니다.
예를 들어, 오브젝트의 알파 값을 점차 줄여 보이지 않게 만드는 작업을 생각해 봅시다. 일반적인 메서드로는 이 작업이 단일 프레임 업데이트 내에서 즉시 완료되어 중간 값이 보이지 않습니다. 코루틴을 사용하면 이 문제를 해결할 수 있습니다.
C#에서 코루틴은 IEnumerator
반환 타입과 바디 어딘가에 포함된 yield return
문으로 선언하는 메서드입니다.
IEnumerator Fade() {
Color c = renderer.material.color;
for (float alpha = 1f; alpha >= 0; alpha -= 0.1f) {
c.a = alpha;
renderer.material.color = c;
yield return null; // 여기서 실행이 일시 정지되고 다음 프레임에 다시 시작됩니다.
}
}
yield return null
라인은 실행이 일시 정지되고 다음 프레임에서 다시 시작되는 지점입니다. 코루틴 실행을 시작하려면 StartCoroutine
함수를 사용해야 합니다.
void Update() {
if (Input.GetKeyDown("f")) {
StartCoroutine(Fade()); // 코루틴 시작
}
}
코루틴 내의 루프 카운터나 다른 변수들은 yield
문이 호출되는 동안에도 올바른 값을 유지하며 상태가 보존됩니다.
시간 지연을 도입하려면 WaitForSeconds
를 사용합니다.
IEnumerator Fade() {
Color c = renderer.material.color;
for (float alpha = 1f; alpha >= 0; alpha -= 0.1f) {
c.a = alpha;
renderer.material.color = c;
yield return new WaitForSeconds(.1f); // 0.1초 대기 후 재개
}
}
WaitForSeconds
를 사용하면 작업을 매 프레임마다 호출되는 Update
메서드 대신 코루틴에 넣어 특정 시간 간격으로 업데이트할 수 있습니다. 예를 들어, 적이 근처에 있는지 확인하는 작업을 매 프레임마다 호출하면 오버헤드가 클 수 있지만, 코루틴을 사용하여 0.1초마다 호출하도록 만들면 게임플레이에 눈에 띄는 영향 없이 검사 횟수를 줄일 수 있습니다.
코루틴 정지:
코루틴은 StopCoroutine
과 StopAllCoroutines
를 사용하여 명시적으로 정지할 수 있습니다. 코루틴이 연결된 게임오브젝트를 비활성화하기 위해 SetActive(false)
로 설정하면 코루틴이 정지됩니다. Destroy(example)
와 같이 MonoBehaviour 인스턴스를 파괴하면 OnDisable
이 즉시 트리거되고 Unity는 코루틴을 처리하여 효과적으로 정지시키며, OnDestroy
는 프레임 끝에 호출됩니다. 하지만 MonoBehaviour를 enabled = false
로 비활성화하는 경우에는 코루틴이 정지되지 않습니다.
성능 분석:
Unity에서 코루틴의 코드는 성능 트레이스에서 두 곳에 나타납니다. 코루틴의 시작 코드(첫 번째 yield
문까지)는 StartCoroutine
호출 시점이나 Unity 콜백에서 나타나고, 나머지 코드(재개 시점부터 종료까지)는 Unity 메인 루프의 DelayedCallManager
라인에서 나타납니다. C# 컴파일러가 코루틴 지원을 위해 생성하는 클래스 인스턴스가 상태를 추적하며, 로컬 변수도 이 클래스로 옮겨져 힙에 할당된 상태로 유지됩니다. 이 오브젝트가 yield
호출 이후 어느 부분부터 다시 시작해야 하는지를 기억합니다.
코루틴을 시작할 때의 메모리 사용량은 고정된 오버헤드 할당에 로컬 범위 변수의 크기를 합한 양과 같습니다. Unity Profiler를 사용하여 코루틴 실행 부분을 검사할 수 있습니다. 코드 명료성을 위해 중첩 코루틴을 사용할 수 있지만, 오브젝트를 더 많이 추적하기 때문에 메모리가 더 소모됩니다. 만약 코루틴이 매 프레임 실행되고 오랫동안 yield
되지 않는다면, Update
또는 LateUpdate
콜백으로 대체하는 것이 더 효율적입니다.
yield return
은 코루틴에서 실행을 일시 중단하고 제어를 반환하는 키워드입니다. C#에서 코루틴 메서드를 선언할 때 사용되는 필수적인 부분입니다.
yield return
의 주요 의미는 다음과 같습니다:
yield return
문을 만나면, 현재 실행을 멈추고 제어를 코루틴을 호출한 곳(예: Unity 엔진의 메인 루프)으로 넘깁니다.yield return
이 호출되는 동안 코루틴의 현재 상태(예: 루프 카운터, 로컬 변수 값 등)는 그대로 유지됩니다. C# 컴파일러는 코루틴 지원을 위해 별도의 클래스를 생성하여 이 상태를 힙에 할당된 형태로 유지 관리합니다.yield return
문은 코루틴이 나중에 어디서부터 실행을 다시 시작해야 하는지를 표시하는 역할을 합니다.yield return
뒤에 오는 값에 따라 언제 다시 실행될지가 결정됩니다.yield return null
: 기본적으로 다음 프레임에서 코루틴 실행을 다시 시작합니다.yield return new WaitForSeconds(.1f)
: 지정된 시간(예: 0.1초)만큼 대기한 후 코루틴 실행을 다시 시작합니다.yield return
타입(예: WaitForEndOfFrame
, AsyncOperation
등)은 특정 조건이 충족될 때까지 대기하도록 지시할 수 있습니다 (이 정보는 제공된 소스에 명시적으로 나와있지 않으나, Unity 코루틴의 일반적인 사용법에 해당합니다).요약하자면, yield return
은 코루틴을 일반 함수와 구분 짓는 핵심 메커니즘으로, 코루틴이 시간의 흐름에 따라 여러 프레임에 걸쳐 실행되거나 비동기 작업을 기다릴 수 있도록 실행 흐름을 유연하게 제어하는 역할을 합니다.
C# 외에도 PHP나 Python 등 여러 언어에서 코루틴이나 제너레이터를 구현할 때 yield
키워드를 사용하여 이와 유사한 중단 및 재개 기능을 제공합니다.
코루틴은 실행을 일시 정지하고 재개하는 기능을 통해 시간의 흐름에 따른 작업, 비동기 I/O, 효율적인 자원 관리 등을 가능하게 하는 강력한 프로그래밍 구조입니다. 스레드와는 다른 협력적 방식을 사용하며, 스레드보다 가볍고 컨텍스트 전환 비용이 낮다는 장점이 있습니다. Unity와 같은 게임 엔진이나 다양한 현대 프로그래밍 언어에서 비동기 및 동시성 처리를 위한 핵심적인 개념으로 활용되고 있습니다. 코루틴과 스레드는 서로 보완적으로 사용될 때 가장 큰 효과를 발휘합니다.