목차
01. 기본 개념
02. 사용 방법
03. 스레드 상태
04. 스레드 동기화
01. 기본 개념
프로세스
: 데이터와 코드가 메모리에 올라가 동작하는 것, 운영체제로부터 자원을 할당받아 실행되는 독립적인 실행 단위
스레드(Thread)
: 프로세스가 할당받은 자원을 이용하는 실행 흐름의 작은 단위
운영체제가 CPU에 시간을 할당하는 기본 단위
프로세스 내에서 실행되며, 프로세스의 자원을 공유함
싱글 스레드
- 하나의 작업만 순차적으로 진행, 유니티에서 메인 로직은 싱글 스레드로 작동
(유니티에서 Update(), FixedUpdate() 등은 메인 스레드에서 실행)
멀티 스레드
- 장점 : 동시에 여러 작업이 가능, 데이터 공유가 쉬움, 메모리 절약 가능, 응답성 향
- 단점 : 구현이 복잡함, 소프트웨어 안정성이 낮아짐, 성능 저하 가능성이 있음(Context Switching)\\
예를 들어 Race Condition 문제나 DeadLock 문제가 발생 가능
Race Condition : 동시에 하나의 자원에 접근할 때 발생하는 문제
이 문제를 해결하기 위해, 하나의 자원에 접근할 때 Lock을 걸어 다른 사람이 접근 못하게 하기도 함
그러나 이는 DeadLock 문제를 야기함. (여러 개의 Lock이 서로얽혀 있는 경우)
DeadLock : Lock이 걸려 있는 자원에 접근할 때 서로 락이 걸려 있어 진행이 멈춤
02. 사용 방법
기본적으로는 메인 스레드가 있기에 ,
Thread thread = Thread.CurrentThread;
Debug.Log(thread.ThreadState);
이런 식으로 현재 스레드의 상태를 확인할 수 있다. ( 유니티에서 접근 가능한 하나의 메인 스레드)
유니티 메인 스레드에선 유니티에서 제공하는 transform, GameObject 등의 내부 기능들을 모두 사용할 수 있다.
다만, 새롭게 스레드를 만든다면 이 내부 제공 기능들을 직접적으로 사용할 수 없고 다른 방식을 사용해야 하기 때문에
보통 프레임 기반 비동기 실행 방식인 코루틴을 사용한다(스레드가 아니다)
스레드를 선언하고 멀테 스레드를 사용할 수 있다.
Thread thread;
thread = new Thread(~~);
저 물결칠 부분에는 ThreadStart , ParametrizedThreadStart 가 들어가고 이 둘 모두 델리게이트이다.
그래서 원하는 함수를 따로 만들고
thread = new Thread(new ThreadStart(Temp));
thread.Start();
이와 같은 방식으로 스레드를 실행시킬 수 있다. (Temp는 임의의 함수)
* ParameterizedThreadStart는 매개변수가 있는 델리게이트, 하나의 매개변수만 전달할 수 있으며, 여러개의 데이터를 전달하려면 객체(클래스 , 구조체 ) 등을 사용하여 묶어서 전달해야 함.
다른 스레드 관련 메서드들
- Thread.Join() : 현재 스레드가 다른 스레드의 작업이 완료될 때까지 대기 , 스레드 동기화 중요한 역할
(지정된 스레드.Join()을 하면 스레드가 이 작업이 완료될 때까지 대기한다) - Thread.Abort() : 강제 종료 메서드, 위험하므로 잘 쓰지 않는다. Exception을 던지고 멈춘다.
- Thread.Interrupt() : 스레드가 WaitSleepJoin 상태가 될 때까지 대기하다가 된다면 종료한다. 조금 더 안전하다.
- Thread.IsBackground : 스레드를 백그라운드 스레드로 만든다. 백그라운드 스레드는 프로그램의 실행 종료에 영향을 미치지 않는다. 즉 모든 포그라운드 스레드가 종료되면 남아 있는 백그라운드 스레드는 강제 종료된다.
03. 스레드 상태 ( Thread State)
ThreadState
- Running : 스레드가 CPU를 점유하여 코드를 실행하고 있는 상태
- StopRequested : 스레드가 외부 요청에 의해 종료를 시도하는 상태, 즉시 종료가 아니라 종료 요청을 처리하는 방식에 따라 종료 시점이 결정됨.
- SuspendRequested : 스레드가 Suspend 할 것을 요청받은 상태 마찬가지로 즉시 종료가 아니다.
- Background : 프로세스의 수명에 영향을 받을지 안받을지를 결정하는 상태, 응용 프로그램 종료시 같이 종료
"The Start() method has not been invoked on the Thread" 라는 설명은 스레드가 Unstarted 상태임을 의미.
즉 생성되었지만 Start() 되지 않아 운영체제에 등록이 안되어 있고, 프로그램의 수명에 영향을 주지 않음. - Unstarted : 스레드가 생성되었지만 Start() 메서드가 호출되지 않은 상태
- Stopped : 스레드의 실행이 완전히 종료된 상태, run() 메서드의 실행을 마치거나 예외가 발생하여 종료된 경우
- WaitSleepJoin : 스레드가 특정 조건이나 이벤트가 발생할 때까지 대기하는 상태
- Suspend : 스레드를 일시중지 시킨 상태 Resume()으로 재개 가능/// 현재 사용되지 않을 수도 있음
- AbortRequested : 스레드를 강제종료 시킬 것을 요청받은 상태, 마찬가지로 즉시 종료는 아니다.
- Aborted : 스레드가 강제 종료된 상태 /// 현재는 사용되지 않을 수 있음
이런 것은 .NET 버전에 따라 다소 차이가 있을 수 있고 정확하지 않으므로 직접 찾아보는 것을 권장.
https://learn.microsoft.com/en-us/dotnet/api/system.threading.threadstate?view=net-9.0
ThreadState Enum (System.Threading)
Specifies the execution states of a Thread.
learn.microsoft.com
여기서 봐야 한다.
https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.threadstate?view=net-9.0
ThreadState Enum (System.Diagnostics)
Specifies the current execution state of the thread.
learn.microsoft.com
이건 Diagnostics 라는 측정 , 로깅 , 디버깅 , 추적 등의 기능을 제공하는 네임스페이스이다.
04. 스레드 동기화
스레드 동기화는 여러 스레드가 공유 자원에 접근할 때 발생하는 문제를 해결하는 방식이다.
위에서 말했던 Race Condition 문제와 Deadlock 문제가 대표적이다.
예를 들어 보자면 , 두 개의 스래드가 Num라는 자원에 접근해 1씩 그 숫자를 더해준다.
순서대로 더해지면 좋겠지만, 다 더해지고 저장하기 전에 다른 스레드가 접근해서 숫자를 바꿨다면 올바르게 결과가
반영되지 않는다. 이것이 Race Condition 문제이다.
이런 문제를 해결하기 위해 "lock"이라는 키워드를 사용할 수 있다.
lock 키워드를 사용해 Critical Section(한 번에 한 스레드만 사용할 수 있는 코드 영역)을 만들고 이를 활용해 문제를 해결한다. 보통 lock 객체의 타입은 참조타입이면 괜찮으나 String, this, typeof(클래스명) 등의 값은 피해야 한다.
외부에서 동일한 인스턴스를 사용하면 예상치 못한 동기화 문제가 발생할 수 있기 때문이다.
lock은 그저 이름을 구분하기 위한 객체일 뿐이다.
*Interlocked 라는 클래스도 존재( 원자적 연산 - atomic operation) : lock 없이도 동기화된 연산 수행 가능
(ex : Interlcked.Increment( ref value))
이 lock 키워드는 내부적으로 Monitor.Enter와 Monitor.Exit를 사용하여 하나의 스레드만 Critical Section을 실행하도록 보장한다. 그러나 lock 은 대기하는 스레드의 실행 순서를 보장하지 않기 때문에 순서 제거가 필요하다면
Monitor를 직접 사용해 Wait()와 Pulse()를 통해 실행 순서를 제어할 수 있다.
https://www.youtube.com/channel/UC1vY8HLaBPPzphSV4-8ZcLw/videos
게이머TV
게임을 만들고 싶어하는 사람들을 위한 채널입니다.
www.youtube.com
유니티에서는 스레드보다는 코루틴이나 async/await 기능을 활용하는 것이 적절할 수 있다.
다음에는 Task 관련해서도 알아볼 것이다.
'개념공부' 카테고리의 다른 글
Task, Async/Await (0) | 2025.02.22 |
---|---|
생성자(Constructor) (0) | 2025.02.18 |
커맨드 패턴(Command Pattern) (0) | 2025.02.14 |
클로저(Closure) (0) | 2025.02.12 |
Unity Event System (0) | 2025.01.25 |