1. 개요
서버를 공부하면서 멀티스레드와 유니티에서의 비동기의 관계가 헷갈려서 정리했었던 내용의 일부이다.
최근에 같이 공부하던 친구가 궁금하다고 해서 유니티부분만 정리해 줬는데, 그 내용을 공유한다.
2. 기본 개념
2.1 동기 vs 비동기
동기(Synchronous):
작업이 순차적으로 실행되며, 각 작업은 이전 작업이 완료될 때까지 기다린다.
장점:
- 코드의 흐름이 예측 가능하고 디버깅이 쉽다.
- 데이터 일관성을 유지하기 쉽다.
단점:
- I/O 작업 등으로 인한 대기 시간 동안 리소스가 낭비될 수 있다.
- 사용자 인터페이스가 응답하지 않을 수 있다.
비동기(Asynchronous):
작업이 병렬적으로 실행되며, 한 작업의 완료를 기다리지 않고 다음 작업을 시작할 수 있다.
장점:
- I/O 바운드 작업에서 효율적이며, UI의 응답성을 향상시킬 수 있다.
- 리소스 사용을 최적화할 수 있다.
단점:
- 코드의 흐름이 복잡해질 수 있으며, race condition 등의 동시성 문제가 발생할 수 있다.
- 디버깅이 더 어려울 수 있다.
2.2 블로킹 vs 논블로킹
블로킹(Blocking):
작업이 완료될 때까지 프로그램의 실행을 멈추고 기다린다.
장점:
- 코드가 직관적이고 이해하기 쉽다.
- 순차적 실행이 필요한 작업에 적합
단점:
- 블로킹 시간이 길어지면 리소스 사용이 비효율적이다.
- 대기 시간 동안 다른 작업을 수행할 수 없다.
논블로킹(Non-blocking):
작업의 완료를 기다리지 않고 프로그램이 계속 실행된다.
장점:
- 리소스를 효율적으로 사용할 수 있다.
- 여러 작업을 동시에 처리할 수 있다.
단점:
- 코드의 복잡성이 증가할 수 있다.
- 작업 완료 시점을 관리하는 것이 더 어려울 수 있다.
2.3 병렬성 vs 동시성
병렬성(Parallelism):
여러 작업이 실제로 동시에 실행된다.(멀티코어 환경에서)
장점:
- CPU 바운드 작업에서 성능을 크게 향상시킬 수 있다.
- 복잡한 계산 작업을 빠르게 처리할 수 있다.
단점:
- 데이터 레이스와 같은 동시성 문제를 주의해야 한다.
- 모든 하드웨어에서 동일한 성능 향상을 기대할 수 없다.
동시성(Concurrency):
여러 작업이 동시에 진행되는 것처럼 보이지만, 실제로는 빠르게 전환되며 실행된다.
장점:
- 단일 코어에서도 효율적인 작업 처리가 가능하다.
- I/O 바운드 작업에 특히 효과적이다.
단점:
- 실제 병렬 실행에 비해 성능 향상이 제한적일 수 있다.
- 작업 전환 오버헤드가 발생할 수 있다.
3. C#(유니티)에서의 비동기 프로그래밍
3.1 Task와 Task<T>
Task: 비동기 작업을 나타내는 객체
Task<T>: 결과를 반환하는 비동기 작업
ex)
using System;
using System.Threading.Tasks;
public class TaskEx : MonoBehaviour
{
static async Task Main()
{
Console.WriteLine("작업 실행");
int result = await LongOperation();
Console.WriteLine($"결과: {result}");
}
static async Task<int> LongOperation()
{
await Task.Delay(2000); // 시간이 걸리는 작업
return 0;
}
}
3.2 async와 await 키워드
async: 메서드가 비동기적으로 실행될 수 있음을 나타냄
await: 비동기 작업의 결과를 기다림
ex)
using UnityEngine;
using System.Threading.Tasks;
public class AsyncEx : MonoBehaviour
{
async void Start()
{
Debug.Log("작업 실행");
int result = await LongOperation();
Debug.Log($"결과: {result}");
}
async Task<int> LongOperation()
{
await Task.Delay(2000); // 시간이 걸리는 작업
return 0;
}
}
3.3 코루틴(Coroutine)
유니티에서 제공하는 비동기 프로그래밍 방식
ex)
using UnityEngine;
using System.Collections;
public class CoroutineEx : MonoBehaviour
{
void Start()
{
StartCoroutine(LongOperation());
}
IEnumerator LongOperation()
{
Debug.Log("작업 실행");
yield return new WaitForSeconds(2);
Debug.Log("작업 종료");
}
}
4. 코루틴
4.1. 기본개념
- 코루틴은 비동기적으로 동작하지만, 실제로는 메인 스레드에서 순차적으로 실행된다.
- 일정 시간 동안 실행을 일시 중지하고 나중에 재개할 수 있다.
- 보통 프레임 단위로 실행되며, 각 yield 지점에서 다음 프레임까지 실행을 일시 중지한다.
4.2. 동작 예시
시간 단위 실행:
IEnumerator MyCoroutine()
{
Debug.Log("코루틴 시작");
yield return new WaitForSeconds(1f);
Debug.Log("1초 후");
}
프레임 단위 실행:
IEnumerator MyCoroutine()
{
for (int i = 0; i < 5; i++)
{
Debug.Log($"프레임 {Time.frameCount}: 카운트 {i}");
yield return null; // 다음 프레임까지 대기
}
}
여러 코루틴의 동시 실행:
void Start()
{
StartCoroutine(Coroutine1());
StartCoroutine(Coroutine2());
}
IEnumerator Coroutine1()
{
while (true)
{
Debug.Log("Coroutine1");
yield return new WaitForSeconds(1f);
}
}
IEnumerator Coroutine2()
{
while (true)
{
Debug.Log("Coroutine2");
yield return new WaitForSeconds(0.5f);
}
}
두 코루틴이 동시에 실행되는 것처럼 보이지만, 실제로는 메인 스레드에서 번갈아가며 실행
5. async/await와 코루틴 비교
5.1. 차이점 및 장단점
실행 모델:
- async / await: 비동기 작업이 완료될 때까지 현재 함수의 실행을 일시 중단
- 코루틴: 실행을 일시 중지하고 나중에 재개
언어/프레임워크 지원:
- async / await: C#의 언어 기능으로 .NET Framework에서 사용
- 코루틴: 유니티 엔진에서 제공하는 기능으로, MonoBehaviour를 상속받은 클래스에서 사용 가능.
예외 처리:
- async / await: try-catch 블록을 사용하여 예외를 직접 처리 가능
- 코루틴: 예외 처리가 복잡하며, 직접 처리 메커니즘을 구현해야 함
취소:
- async / await: CancellationToken을 사용하여 작업을 취소 가능
- 코루틴: StopCoroutine을 사용하여 코루틴을 중지할 수 있지만, 세밀한 제어가 어려움
성능:
- async / await: 일반적으로 더 높은 오버헤드를 가지지만, 복잡한 비동기 로직을 처리하는 데 유리.
- 코루틴: 상대적으로 낮은 오버헤드를 가지며, 간단한 비동기 작업에 효율적
5.2. 실제 사용 시나리오
코루틴 사용이 적합한 경우:
1) 프레임 단위로 실행되어야 하는 작업
2) 유니티의 시간 기반 기능(예: WaitForSeconds)을 사용해야 하는 경우
3) 간단한 비동기 작업(예: 애니메이션, 타이머)
4) 유니티 API와 밀접하게 연동되는 작업
async/await 사용이 적합한 경우:
1. 복잡한 비동기 로직이 필요한 경우
2. 외부 API 호출이나 네트워크 작업
3. 파일 I/O 작업
4. 긴 시간이 걸리는 계산 작업
6. 코루틴 vs 멀티스레딩
동시성 vs 병렬성:
- 코루틴: 동시성(Concurrency)을 제공. 작업들이 번갈아가며 실행되어 동시에 실행되는 것처럼 보임
- 멀티스레딩: 병렬성(Parallelism)을 제공. 작업들이 실제로 동시에 다른 CPU 코어에서 실행
스레드 안전성:
- 코루틴: 항상 메인 스레드에서 실행되므로 쓰레드 안전성 문제가 없음
- 멀티스레딩: 동시에 여러 쓰레드가 같은 데이터에 접근할 수 있어 race condition 등의 문제가 발생할 수 있음
성능:
- 코루틴: CPU 바운드 작업에는 적합하지 않지만, I/O 바운드 작업이나 시간 지연이 필요한 작업에 효율적
- 멀티쓰레딩: CPU 바운드 작업에 적합하며, 실제로 병렬 처리가 가능
사용 복잡도:
- 코루틴: 상대적으로 사용하기 쉽고, 유니티의 생명주기와 잘 통합됨
- 멀티쓰레딩: 더 복잡하며, 동기화, 데드락 방지 등 추가적인 고려사항이 필요
7. 마무리
마지막 멀티스레딩은 C++ 서버를 공부하던 내용입니다.
유니티에서도 멀티스레드를 사용할 수 있긴 하지만, 기본적으로 단일스레드 동작이라 유니티만 쓰는 사람이면 당장은 크게 신경 안 써도 될듯합니다.
'Unity' 카테고리의 다른 글
Delegate, Action, Event (1) | 2024.11.07 |
---|---|
벡터 (0) | 2024.11.01 |
Null 관련 연산자 (3) | 2024.10.31 |
Rigidbody.velocity 천천히 떨어지는 문제 (0) | 2024.04.21 |