[Unity] 코루틴과 yield

Running boy·2023년 9월 4일
0

유니티

목록 보기
8/9

누군가 나에게 코루틴에 대해 물어본다면 어떻게 설명할 수 있을까?
코루틴을 구성하는데 yield가 반드시 필요한 이유가 무엇일까?

새벽에 일어나 공부를 하는 와중에 문득 이런 생각을 하게 됐다.

지금까지 나는 코루틴에서 yield의 역할이 프로그램 실행의 제어권을 넘겨준다고 해왔다.
근데 사실 이는 반만 맞는 말이다.
프로그램 내부의 흐름으로 봤을 때 코루틴의 yield는 다른 코루틴이나 메서드로 제어권을 넘겨주는 것이 맞다.
하지만 운영체제의 스케쥴링 관점에서 yield는 제어권을 넘겨주지 않는다.

이 설명이 잘 이해가 되지 않는가?
그렇다면 우선 이 두가지 질문에 대한 답을 생각해보자.

  1. 코루틴은 비동기 작업 방식의 패러다임인가?
  2. 코루틴은 멀티 스레딩 작업 방식의 패러다임인가?

비동기와 멀티 스레딩의 구분

일단 비동기와 멀티 스레딩은 서로 전혀 연관이 없는 별개의 개념이다.
비동기'작업을 병렬로 처리하여 서로 다른 작업이 영향을 미치지 않게끔 진행되는 작업 방식'을 의미하며, 멀티 스레딩'작업을 여러 작은 단위(스레드)로 분할하여 처리하는 방식'을 의미한다.

멀티 스레딩은 비동기 방식(서로 다른 CPU 코어를 점유)일 수도 있고 동기 방식(동일한 공유 자원에 접근)일 수도 있다.
비동기 작업은 멀티 스레딩일 수도 있고 싱글 스레딩일 수도 있다.
이렇듯 둘은 아무런 연관이 없으며 그저 작업을 처리하는 별개의 패러다임일 뿐이다.

그런데 왜 갑자기 뜬금없이 비동기와 멀티 스레딩 얘기를 하는가?
코루틴에 대해 설명하려면 이 개념에 대해 확실히 이해할 필요가 있기 때문이다.

코루틴의 탄생 배경

코루틴이라는 개념 자체는 기존부터 존재했다.
유니티 역시 초기 버전부터 코루틴 기능을 포함해서 출시했다.
다만 유니티에서 코루틴은 조금 특별한 의미를 갖는다.

기존의 .NET 프로그래밍에선 Thread나 Task 등을 사용해 멀티 스레딩으로 비동기 프로그래밍을 하는 것이 관례였다.
하지만 유니티 엔진은 멀티 스레딩을 지원하지 않는다.
정확히는 메인 스레드를 제외한 서브 스레드에서는 유니티의 API를 호출할 수 없다.
왜냐하면 물리 엔진이라는 것은 물리 현상의 다양한 상태와 복잡한 연산을 처리하는 프로그램인데 멀티 스레딩으로 접근할 경우 치명적인 오류가 발생할 수 있기에 유니티 자체에서 막아놨기 때문이다.
그래서 유니티는 비동기 프로그래밍을 지원하도록 코루틴이라는 패러다임을 도입했다.

코루틴의 실체

앞서 설명했듯 유니티는 메인 스레드에서만 실행되기 때문에 코루틴은 단일 스레드에서 동작하는 비동기 작업 방식의 패러다임이다.
즉 코루틴은 멀티 스레딩이 아니기 때문에 운영체제의 스케쥴링과 전혀 연관이 없다.
그렇기 때문에 멀티 스레딩으로 인한 동기화 이슈 역시 전혀 발생하지 않는다.

그렇다면 코루틴은 어떻게 구현됐길래 단일 스레드상에서 비동기 프로그래밍이 가능한 것일까?

우리에게 익숙한 타입인 Enumerable에 대해 잠시 설명하겠다.
Enumerable은 IEnumerable 인터페이스를 상속받고 내부적으로 IEnumerator 인터페이스를 상속받은 클래스가 구현돼있다.
Enumerable을 반복문에서 실행하면 Enumerator를 반환하여 MoveNext의 결과가 false가 될 때까지 Current값을 구한다.

하지만 매번 Enumerable과 Enumerator를 구현하는 것은 귀찮기 때문에 C# 2.0에서 반복자 구현을 단순화하는 'yield' 예약어가 추가되었다.
IEnumerator를 반환하는 메서드에 yield 예약어를 사용하면 컴파일러는 컴파일 시점에 이를 상태 머신(state machine)으로 변환한다.
어딘가 많이 익숙한 형태이지 않은가?

코루틴의 정체는 상태 머신이었던 것이다.
상태 머신은 메서드가 어디서 중단되었는지, 다음에 어디서부터 실행돼야 하는지를 추적한다.
그리고 유니티의 코루틴 시스템은 마치 운영체제처럼 이러한 코루틴들의 실행 흐름을 내부 로직을 통해 조율한다.

조금 더 자세히 설명하면, 코루틴은 매 프레임 호출되어 IEnumerator의 MoveNext 메서드를 호출한다.
이는 yield return까지의 코드가 실행됨을 의미한다.
그리고 yield return으로 반환된 상태에 따라 코루틴의 상태가 설정된다.
예를 들면 yield return null은 상태가 null이기에 다음 프레임에 이후의 코드가 실행되는 것이고 yield return new WaitForSecond(1)은 '1초간 대기한다'라는 상태를 반환했기 때문에 상태가 변경되어 코루틴 시스템에 의해 스케쥴링 된다.

코루틴이란 무엇이며 yield가 갖는 의미는 무엇인가?

지금까지 정리한 내용을 바탕으로 다음의 질문에 다시 한 번 대답해보자.


Q. 코루틴이란 무엇이며 yield가 갖는 의미는 무엇인가?

A. 코루틴은 유니티의 단일 스레드 환경에서 비동기 방식의 프로그래밍을 지원하기 위해 도입된 패러다임이며 실행 도중 일시 중단과 재실행이 가능한 상태 머신이다. yield는 컴파일러에 의해 IEnumerator를 상태 머신으로 변환하게끔 해주는 예약어이며 코루틴은 yield return을 통해 코루틴의 상태를 반환하고 실행을 일시 중단할 수 있다.

profile
Runner's high를 목표로

0개의 댓글