2025. 2. 26. 21:05ㆍComputer Science/C, C++, C#
Thread : 프로세스 보다 작은 실행 단위, 그래서 우스갯 소리로 서브 프로세스라고 한다. 하나의 프로세스 안의 여러 스래드가 메모리를 공유하여 코드를 실행한다. 물론 여기서 발생하는 문제가 상당히 많다...
Task : 원래는 비동기 작업을 추상화해서 만든 클래스이다. 다만 안에서 내부적으로 Thread의 Pool을 만들어서 (Object Pooling 패턴 참고.) 작업을 처리하게 되므로, 쓰레드에 비해서 개발자가 지지고 볶을 필요가 없게 되었다. 더불어 Await/Async를 지원하기에 비동기 프로그래밍을 구현한다.
스레드와 테스크의 메서드나 플래그에 대해서는, 다른 블로그에서 아주 친절하고 자세하게 설명하고 있으므로 생략한다. 다만 이 포스트에서는 Thread를 사용할 때의 몇가지 유의사항에 대해서 기술하고자 한다.
Race Condition
싱글 스레드에서 북치고 장구치던 것과는 다르게, 멀티 스레드로 들어가게 된다면 굉장히 머리가 아픈 경우가 발생한다. 그리고 그 머리 아픈 점 중의 하나는 바로 Race Condition인데, 이는 스레드들이 프로세스에 배정된 자원을 공유하면서 발생한다.
예를 들어서 생각해보자, 스레드 A/B가 있을 때.. 스레드 A가 어떤 자원을 1 증가시키고, 스레드 B는 어떤 자원을 1 감소시킨다. 문제는 "스레드는 한번에 여러개가 실행될 수 있으며" 따라서 A와 B는 동시에 자원에 접근할 수 있는 상황이 펼쳐질 수 있다. 물론 사례를 들자면 끝도 없지만, 어찌 되었던 이러한 경우 끔찍한 재앙이 발생한다!!!
Mutual Exclusive (Mutex)
그래서 나온 것이 상호 배제이다. 쉽게 말해서, 어떤 쓰레드(프로세스)가 공유자원을 먹고 있으면 해당 공유자원에는 다른 쓰레드가 접근하지 못하게 막자는 발상이다. C#에서는 lock 키워드로 설정할 수 있는데, 아래 예시 코드를 참조하시라.
class Program
{
// Ref : https://lab.cliel.com/entry/C-%EC%8A%A4%EB%A0%88%EB%93%9C%EC%99%80-%ED%83%9C%EC%8A%A4%ED%81%AC
static int count = 0;
static readonly object lck = new Object();
static void Main(string[] args)
{
Thread t1 = new Thread(Working);
Thread t2 = new Thread(Working);
Thread t3 = new Thread(Working);
t1.Start();
t2.Start();
t3.Start();
t1.Join();
t2.Join();
t3.Join();
WriteLine(count);
}
static void Working()
{
lock (lck)
{
++count;
}
}
}
Coroutine
유니티에서는 매 프레임이 갱신될 때 마다 Update()가 호출된다. 다만 여기에 반복이 필요한 로직을 떡칠하게 될 경우 - 성능에 지대한 영향이 생기게 된다. 따라서 코루틴을 사용해서 특정 Clock으로 반복문을 외부에 돌리기도 한다.
이때 일부 독자들은 Thread/Task를 사용할 수 있지 않느냐고 물어 볼 수 있으나, 유감스럽게도 유니티는 싱글스레드를 전제로 한다. 사실 멀티스레딩 지원이 안되는 건 아니나, 상당히 머리가 아파진다. 일례로 시티즈:스카이라인즈는 4~8스레드(정확한 숫자가 기억이 안난다.)를 지원하는데, 이때문에 유니티 본사에서 직원을 직접 파견해서 최적화에 피눈물을 흘렸다고 한다.
IEnumerator loop(){
yield return; // 이 경우 다음 프레임에 실행.
// yield return new WaitForSeconds(float); 매개 변수로 넘긴 숫자만큼 대기.
// yield return new WaitForSecondsRealtime(float); Documentation 참고.
// yield break; 끝내기.
}
StartCoroutine(loop());
코루틴의 사용법은 위와 같다. 이는 Invoke()와 비교했을때, 주기 설정이 가능하고 파라미터를 손쉽게 넘길 수 있다는 점에서 매우 장점이 크다고 할 것이다.