Unity Game Dev : 유니티의 비동기 처리와 async 와 await 의 작동 방식.
안녕하세요 이번글에선 제가 개발중 알게된 유니티의 비동기 처리방식인 async와 await의 개념 그리고 그 작동 방식에 대해서 다뤄 보겠습니다. 제 스스로 익힌 개념을 정리하기 위한 목적도 있지만, 제 블로그에 찾아오신 분들을 위해 최대한 이해하기 쉽게 글로 풀어 쓰고자 노력하였습니다.
비동기 처리란 무엇인가?
비동기 처리란, 유니티의 Main Thread를 멈추지 않고, 시간이 오래 걸리는 작업을 뒤에서 처리하거나, 여러 프레임에 나누어 실행하는 기법입니다. Main Thread란, 유니티가 기본적으로 처리하는, 프레임 업데이트, 렌더링, 물리 연산, UI 업데이트 등을 처리하는 Thread를 말합니다.
[Tips : Thread(스레드)란, 운영체제가 CPU 사용 시간을 할당하는 가장 작은 작업 실행 단위 입니다. 우리가 프로그램을 실행시키면 이는 프로세스가 생성되어 실행되는데, 프로세스는 1개 이상의 스레드를 생성하게 됩니다. 결국 스레드는 프로세스 안에서 실행되는 흐름의 단위를 말합니다!]
[Tips : 유니티가 이 수많은 작업을 메인 스레드에 의존하는 이유는 안정성 때문입니다. 여러 스레드가 동시에 한가지 데이터를 바꾸려 한다면 그 과정에서 데이터가 이상한 값으로 바뀌거나, 예상치 못한 값이 지정되는등, 여러가지 문제가 생길 수 있습니다. 때문에 유니티는 기본적으로 주 기능들을 Main Thread 안에서 처리하는 것입니다]
그렇다면 비동기 처리는 왜 필요한 것이고, 어디에서 쓰이는 걸까요?
유니티에는 async/await을 이용한 방법 외에도 여러가지 비동기 처리 방식이 있습니다. 이번 글에서는 async/await 방식에 대하여 집중적으로 다루어 보겠습니다.
해당 방식은 "시간이 얼마나 걸릴지 예측하기 어렵거나 유의미하게 대기 시간이 발생하는 작업"을 처리할 때 사용합니다.
유니티의 Main Thread는 기본적으로 동기 방식으로 작업을 처리합니다. 때문에 먼저 실행되고 있는 작업이 끝나기 전까지는 다음 작업을 이어 나갈 수 없습니다. 예를 들어보겠습니다. 만약 어떤 작업이 너무 오래 걸린 나머지 그것이 1초 가량 걸렸다면, 게임은 1초동안 프레임 렌더링과 물리연산같은 핵심 처리를 모두 정지하게 됩니다. 1초 걸리는 작업이 모두 끝날때까지 다음 작업을 할 수 없기 때문이죠. 이는 플레이어에게 흔히 말하는 렉(Lag)으로 보여지게 됩니다. 게임에서 렉은 플레이어에게 수많은 불쾌한 경험으로 이어질 수 있기 때문에 개발자들은 최대한 이 현상을 방지해야 합니다.
게임 개발에 있어서 필요한 모든 연산과 작업이 프레임 시간보다 빠르게 처리된다면 좋겠지만, 네트워크 통신 / 파일 및 데이터베이스 접근 / 복잡한 연산 (예 : A* Pathfinding) 은 더 많은 시간을 필요로 합니다. 만약 이러한 연산을 그냥 작업한다면 수많은 렉이 발생할 수 있습니다. 때문에 이러한 작업은 동기 방식으로 처리할 수 없는 것입니다.
그래서 복잡하고 오래 걸리는 작업들을 async/await 비동기 방식으로 처리하여 게임 렉을 최소화 할 수 있습니다.
Async / Await 은 어떻게 작동하나요?
우선 어떤 함수를 비동기 방식으로 처리하기 위해서는 함수 호출시 await 키워드를 사용하여야 합니다. await으로 비동기 방식으로 함수를 호출하기 위해서는 함수가 Task/UniTask 타입을 return해야 합니다. 그리고 await으로 함수를 호출하기 위해서는 await caller가 포함된 함수의 선언에 async 키워드가 포함 되어야 합니다.
await을 붙여 호출할 경우, Callee 내부가 모두 실행되어 return되기 전까지는 await으로 호출한 caller의 다음 부분을 실행하지 않고, Main Thread의 다음 작업들을 이어서 실행합니다. 그러다가 async callee의 return을 감지하면 caller 다음의 코드를 실행하는 것입니다.
[Caller 는 함수를 호출하는 코드를 말합니다. Callee를 호출당하는 함수를 말합니다.]
정리하자면, await 키워드는 호출한 비동기 함수가 모두 실행되기 전까지는 다음 코드를 실행하지 않고, 작업이 완료되기까지 대기하겠다는 뜻입니다.
정리하자면, await 키워드는 호출한 비동기 함수가 모두 실행되기 전까지는 다음 코드를 실행하지 않고, 작업이 완료되기까지 대기하겠다는 뜻입니다.
Async / Await 은 Multi - Thread 에서 작동하나요?
비동기 방식이고, 아까 Main Thread가 주로 동기 방식으로 작업을 처리하기 때문에 Multi Thread를 이용해 비동기 작업을 처리한다고 생각하실 수 있습니다. 이것에 대해 설명하자면, 개발자가 별도의 처리를 하지 않을경우, 기본적으로 Main Thread에서 작업 교차 방식으로 비동기 작업을 수행하게 됩니다. 그러나 명시적으로 Multi Thread를 사용하도록 지정 할 수 있습니다. 대규모 연산작업에 이러한 방식이 효과적입니다.
그러나 멀티스레드가 만능인것은 아닙니다. 유니티 엔진의 핵심 API인 Transform.postion / GameObject.Instantiate / UnityEngine.UI 등은 Thread-safe하지 않기 때문에 Main Thread에서만 호출이 가능합니다. 따라서 멀티스레드는 유니티 API와는 상관없는 독립적인 고도 연산 작업에 사용하는 것이 적합합니다.
댓글
댓글 쓰기