개념공부

Thread

Cadi 2025. 2. 16. 02:04

 

목차

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

 

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